cregit-Linux how code gets into the kernel

Release 4.11 drivers/usb/gadget/udc/bdc/bdc_ep.c

/*
 * bdc_ep.c - BRCM BDC USB3.0 device controller endpoint related functions
 *
 * Copyright (C) 2014 Broadcom Corporation
 *
 * Author: Ashwini Pahuja
 *
 * Based on drivers under drivers/usb/
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 */
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/dmapool.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/pm.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <asm/unaligned.h>
#include <linux/platform_device.h>
#include <linux/usb/composite.h>

#include "bdc.h"
#include "bdc_ep.h"
#include "bdc_cmd.h"
#include "bdc_dbg.h"


static const char * const ep0_state_string[] =  {
	"WAIT_FOR_SETUP",
	"WAIT_FOR_DATA_START",
	"WAIT_FOR_DATA_XMIT",
	"WAIT_FOR_STATUS_START",
	"WAIT_FOR_STATUS_XMIT",
	"STATUS_PENDING"
};

/* Free the bdl during ep disable */

static void ep_bd_list_free(struct bdc_ep *ep, u32 num_tabs) { struct bd_list *bd_list = &ep->bd_list; struct bdc *bdc = ep->bdc; struct bd_table *bd_table; int index; dev_dbg(bdc->dev, "%s ep:%s num_tabs:%d\n", __func__, ep->name, num_tabs); if (!bd_list->bd_table_array) { dev_dbg(bdc->dev, "%s already freed\n", ep->name); return; } for (index = 0; index < num_tabs; index++) { /* * check if the bd_table struct is allocated ? * if yes, then check if bd memory has been allocated, then * free the dma_pool and also the bd_table struct memory */ bd_table = bd_list->bd_table_array[index]; dev_dbg(bdc->dev, "bd_table:%p index:%d\n", bd_table, index); if (!bd_table) { dev_dbg(bdc->dev, "bd_table not allocated\n"); continue; } if (!bd_table->start_bd) { dev_dbg(bdc->dev, "bd dma pool not allocated\n"); continue; } dev_dbg(bdc->dev, "Free dma pool start_bd:%p dma:%llx\n", bd_table->start_bd, (unsigned long long)bd_table->dma); dma_pool_free(bdc->bd_table_pool, bd_table->start_bd, bd_table->dma); /* Free the bd_table structure */ kfree(bd_table); } /* Free the bd table array */ kfree(ep->bd_list.bd_table_array); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja20799.52%150.00%
Colin Ian King10.48%150.00%
Total208100.00%2100.00%

/* * chain the tables, by insteting a chain bd at the end of prev_table, pointing * to next_table */
static inline void chain_table(struct bd_table *prev_table, struct bd_table *next_table, u32 bd_p_tab) { /* Chain the prev table to next table */ prev_table->start_bd[bd_p_tab-1].offset[0] = cpu_to_le32(lower_32_bits(next_table->dma)); prev_table->start_bd[bd_p_tab-1].offset[1] = cpu_to_le32(upper_32_bits(next_table->dma)); prev_table->start_bd[bd_p_tab-1].offset[2] = 0x0; prev_table->start_bd[bd_p_tab-1].offset[3] = cpu_to_le32(MARK_CHAIN_BD); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja104100.00%1100.00%
Total104100.00%1100.00%

/* Allocate the bdl for ep, during config ep */
static int ep_bd_list_alloc(struct bdc_ep *ep) { struct bd_table *prev_table = NULL; int index, num_tabs, bd_p_tab; struct bdc *bdc = ep->bdc; struct bd_table *bd_table; dma_addr_t dma; if (usb_endpoint_xfer_isoc(ep->desc)) num_tabs = NUM_TABLES_ISOCH; else num_tabs = NUM_TABLES; bd_p_tab = NUM_BDS_PER_TABLE; /* if there is only 1 table in bd list then loop chain to self */ dev_dbg(bdc->dev, "%s ep:%p num_tabs:%d\n", __func__, ep, num_tabs); /* Allocate memory for table array */ ep->bd_list.bd_table_array = kzalloc( num_tabs * sizeof(struct bd_table *), GFP_ATOMIC); if (!ep->bd_list.bd_table_array) return -ENOMEM; /* Allocate memory for each table */ for (index = 0; index < num_tabs; index++) { /* Allocate memory for bd_table structure */ bd_table = kzalloc(sizeof(struct bd_table), GFP_ATOMIC); if (!bd_table) goto fail; bd_table->start_bd = dma_pool_alloc(bdc->bd_table_pool, GFP_ATOMIC, &dma); if (!bd_table->start_bd) { kfree(bd_table); goto fail; } bd_table->dma = dma; dev_dbg(bdc->dev, "index:%d start_bd:%p dma=%08llx prev_table:%p\n", index, bd_table->start_bd, (unsigned long long)bd_table->dma, prev_table); ep->bd_list.bd_table_array[index] = bd_table; memset(bd_table->start_bd, 0, bd_p_tab * sizeof(struct bdc_bd)); if (prev_table) chain_table(prev_table, bd_table, bd_p_tab); prev_table = bd_table; } chain_table(prev_table, ep->bd_list.bd_table_array[0], bd_p_tab); /* Memory allocation is successful, now init the internal fields */ ep->bd_list.num_tabs = num_tabs; ep->bd_list.max_bdi = (num_tabs * bd_p_tab) - 1; ep->bd_list.num_tabs = num_tabs; ep->bd_list.num_bds_table = bd_p_tab; ep->bd_list.eqp_bdi = 0; ep->bd_list.hwd_bdi = 0; return 0; fail: /* Free the bd_table_array, bd_table struct, bd's */ ep_bd_list_free(ep, num_tabs); return -ENOMEM; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja34398.00%150.00%
Sudip Mukherjee72.00%150.00%
Total350100.00%2100.00%

/* returns how many bd's are need for this transfer */
static inline int bd_needed_req(struct bdc_req *req) { int bd_needed = 0; int remaining; /* 1 bd needed for 0 byte transfer */ if (req->usb_req.length == 0) return 1; /* remaining bytes after tranfering all max BD size BD's */ remaining = req->usb_req.length % BD_MAX_BUFF_SIZE; if (remaining) bd_needed++; /* How many maximum BUFF size BD's ? */ remaining = req->usb_req.length / BD_MAX_BUFF_SIZE; bd_needed += remaining; return bd_needed; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja70100.00%1100.00%
Total70100.00%1100.00%

/* returns the bd index(bdi) corresponding to bd dma address */
static int bd_add_to_bdi(struct bdc_ep *ep, dma_addr_t bd_dma_addr) { struct bd_list *bd_list = &ep->bd_list; dma_addr_t dma_first_bd, dma_last_bd; struct bdc *bdc = ep->bdc; struct bd_table *bd_table; bool found = false; int tbi, bdi; dma_first_bd = dma_last_bd = 0; dev_dbg(bdc->dev, "%s %llx\n", __func__, (unsigned long long)bd_dma_addr); /* * Find in which table this bd_dma_addr belongs?, go through the table * array and compare addresses of first and last address of bd of each * table */ for (tbi = 0; tbi < bd_list->num_tabs; tbi++) { bd_table = bd_list->bd_table_array[tbi]; dma_first_bd = bd_table->dma; dma_last_bd = bd_table->dma + (sizeof(struct bdc_bd) * (bd_list->num_bds_table - 1)); dev_dbg(bdc->dev, "dma_first_bd:%llx dma_last_bd:%llx\n", (unsigned long long)dma_first_bd, (unsigned long long)dma_last_bd); if (bd_dma_addr >= dma_first_bd && bd_dma_addr <= dma_last_bd) { found = true; break; } } if (unlikely(!found)) { dev_err(bdc->dev, "%s FATAL err, bd not found\n", __func__); return -EINVAL; } /* Now we know the table, find the bdi */ bdi = (bd_dma_addr - dma_first_bd) / sizeof(struct bdc_bd); /* return the global bdi, to compare with ep eqp_bdi */ return (bdi + (tbi * bd_list->num_bds_table)); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja226100.00%1100.00%
Total226100.00%1100.00%

/* returns the table index(tbi) of the given bdi */
static int bdi_to_tbi(struct bdc_ep *ep, int bdi) { int tbi; tbi = bdi / ep->bd_list.num_bds_table; dev_vdbg(ep->bdc->dev, "bdi:%d num_bds_table:%d tbi:%d\n", bdi, ep->bd_list.num_bds_table, tbi); return tbi; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja51100.00%1100.00%
Total51100.00%1100.00%

/* Find the bdi last bd in the transfer */
static inline int find_end_bdi(struct bdc_ep *ep, int next_hwd_bdi) { int end_bdi; end_bdi = next_hwd_bdi - 1; if (end_bdi < 0) end_bdi = ep->bd_list.max_bdi - 1; else if ((end_bdi % (ep->bd_list.num_bds_table-1)) == 0) end_bdi--; return end_bdi; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja65100.00%1100.00%
Total65100.00%1100.00%

/* * How many transfer bd's are available on this ep bdl, chain bds are not * counted in available bds */
static int bd_available_ep(struct bdc_ep *ep) { struct bd_list *bd_list = &ep->bd_list; int available1, available2; struct bdc *bdc = ep->bdc; int chain_bd1, chain_bd2; int available_bd = 0; available1 = available2 = chain_bd1 = chain_bd2 = 0; /* if empty then we have all bd's available - number of chain bd's */ if (bd_list->eqp_bdi == bd_list->hwd_bdi) return bd_list->max_bdi - bd_list->num_tabs; /* * Depending upon where eqp and dqp pointers are, caculate number * of avaialble bd's */ if (bd_list->hwd_bdi < bd_list->eqp_bdi) { /* available bd's are from eqp..max_bds + 0..dqp - chain_bds */ available1 = bd_list->max_bdi - bd_list->eqp_bdi; available2 = bd_list->hwd_bdi; chain_bd1 = available1 / bd_list->num_bds_table; chain_bd2 = available2 / bd_list->num_bds_table; dev_vdbg(bdc->dev, "chain_bd1:%d chain_bd2:%d\n", chain_bd1, chain_bd2); available_bd = available1 + available2 - chain_bd1 - chain_bd2; } else { /* available bd's are from eqp..dqp - number of chain bd's */ available1 = bd_list->hwd_bdi - bd_list->eqp_bdi; /* if gap between eqp and dqp is less than NUM_BDS_PER_TABLE */ if ((bd_list->hwd_bdi - bd_list->eqp_bdi) <= bd_list->num_bds_table) { /* If there any chain bd in between */ if (!(bdi_to_tbi(ep, bd_list->hwd_bdi) == bdi_to_tbi(ep, bd_list->eqp_bdi))) { available_bd = available1 - 1; } } else { chain_bd1 = available1 / bd_list->num_bds_table; available_bd = available1 - chain_bd1; } } /* * we need to keep one extra bd to check if ring is full or empty so * reduce by 1 */ available_bd--; dev_vdbg(bdc->dev, "available_bd:%d\n", available_bd); return available_bd; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja244100.00%1100.00%
Total244100.00%1100.00%

/* Notify the hardware after queueing the bd to bdl */
void bdc_notify_xfr(struct bdc *bdc, u32 epnum) { struct bdc_ep *ep = bdc->bdc_ep_array[epnum]; dev_vdbg(bdc->dev, "%s epnum:%d\n", __func__, epnum); /* * We don't have anyway to check if ep state is running, * except the software flags. */ if (unlikely(ep->flags & BDC_EP_STOP)) ep->flags &= ~BDC_EP_STOP; bdc_writel(bdc->regs, BDC_XSFNTF, epnum); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja68100.00%1100.00%
Total68100.00%1100.00%

/* returns the bd corresponding to bdi */
static struct bdc_bd *bdi_to_bd(struct bdc_ep *ep, int bdi) { int tbi = bdi_to_tbi(ep, bdi); int local_bdi = 0; local_bdi = bdi - (tbi * ep->bd_list.num_bds_table); dev_vdbg(ep->bdc->dev, "%s bdi:%d local_bdi:%d\n", __func__, bdi, local_bdi); return (ep->bd_list.bd_table_array[tbi]->start_bd + local_bdi); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja78100.00%1100.00%
Total78100.00%1100.00%

/* Advance the enqueue pointer */
static void ep_bdlist_eqp_adv(struct bdc_ep *ep) { ep->bd_list.eqp_bdi++; /* if it's chain bd, then move to next */ if (((ep->bd_list.eqp_bdi + 1) % ep->bd_list.num_bds_table) == 0) ep->bd_list.eqp_bdi++; /* if the eqp is pointing to last + 1 then move back to 0 */ if (ep->bd_list.eqp_bdi == (ep->bd_list.max_bdi + 1)) ep->bd_list.eqp_bdi = 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja75100.00%1100.00%
Total75100.00%1100.00%

/* Setup the first bd for ep0 transfer */
static int setup_first_bd_ep0(struct bdc *bdc, struct bdc_req *req, u32 *dword3) { u16 wValue; u32 req_len; req->ep->dir = 0; req_len = req->usb_req.length; switch (bdc->ep0_state) { case WAIT_FOR_DATA_START: *dword3 |= BD_TYPE_DS; if (bdc->setup_pkt.bRequestType & USB_DIR_IN) *dword3 |= BD_DIR_IN; /* check if zlp will be needed */ wValue = le16_to_cpu(bdc->setup_pkt.wValue); if ((wValue > req_len) && (req_len % bdc->gadget.ep0->maxpacket == 0)) { dev_dbg(bdc->dev, "ZLP needed wVal:%d len:%d MaxP:%d\n", wValue, req_len, bdc->gadget.ep0->maxpacket); bdc->zlp_needed = true; } break; case WAIT_FOR_STATUS_START: *dword3 |= BD_TYPE_SS; if (!le16_to_cpu(bdc->setup_pkt.wLength) || !(bdc->setup_pkt.bRequestType & USB_DIR_IN)) *dword3 |= BD_DIR_IN; break; default: dev_err(bdc->dev, "Unknown ep0 state for queueing bd ep0_state:%s\n", ep0_state_string[bdc->ep0_state]); return -EINVAL; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja198100.00%1100.00%
Total198100.00%1100.00%

/* Setup the bd dma descriptor for a given request */
static int setup_bd_list_xfr(struct bdc *bdc, struct bdc_req *req, int num_bds) { dma_addr_t buf_add = req->usb_req.dma; u32 maxp, tfs, dword2, dword3; struct bd_transfer *bd_xfr; struct bd_list *bd_list; struct bdc_ep *ep; struct bdc_bd *bd; int ret, bdnum; u32 req_len; ep = req->ep; bd_list = &ep->bd_list; bd_xfr = &req->bd_xfr; bd_xfr->req = req; bd_xfr->start_bdi = bd_list->eqp_bdi; bd = bdi_to_bd(ep, bd_list->eqp_bdi); req_len = req->usb_req.length; maxp = usb_endpoint_maxp(ep->desc); tfs = roundup(req->usb_req.length, maxp); tfs = tfs/maxp; dev_vdbg(bdc->dev, "%s ep:%s num_bds:%d tfs:%d r_len:%d bd:%p\n", __func__, ep->name, num_bds, tfs, req_len, bd); for (bdnum = 0; bdnum < num_bds; bdnum++) { dword2 = dword3 = 0; /* First bd */ if (!bdnum) { dword3 |= BD_SOT|BD_SBF|(tfs<<BD_TFS_SHIFT); dword2 |= BD_LTF; /* format of first bd for ep0 is different than other */ if (ep->ep_num == 1) { ret = setup_first_bd_ep0(bdc, req, &dword3); if (ret) return ret; } } if (!req->ep->dir) dword3 |= BD_ISP; if (req_len > BD_MAX_BUFF_SIZE) { dword2 |= BD_MAX_BUFF_SIZE; req_len -= BD_MAX_BUFF_SIZE; } else { /* this should be the last bd */ dword2 |= req_len; dword3 |= BD_IOC; dword3 |= BD_EOT; } /* Currently only 1 INT target is supported */ dword2 |= BD_INTR_TARGET(0); bd = bdi_to_bd(ep, ep->bd_list.eqp_bdi); if (unlikely(!bd)) { dev_err(bdc->dev, "Err bd pointing to wrong addr\n"); return -EINVAL; } /* write bd */ bd->offset[0] = cpu_to_le32(lower_32_bits(buf_add)); bd->offset[1] = cpu_to_le32(upper_32_bits(buf_add)); bd->offset[2] = cpu_to_le32(dword2); bd->offset[3] = cpu_to_le32(dword3); /* advance eqp pointer */ ep_bdlist_eqp_adv(ep); /* advance the buff pointer */ buf_add += BD_MAX_BUFF_SIZE; dev_vdbg(bdc->dev, "buf_add:%08llx req_len:%d bd:%p eqp:%d\n", (unsigned long long)buf_add, req_len, bd, ep->bd_list.eqp_bdi); bd = bdi_to_bd(ep, ep->bd_list.eqp_bdi); bd->offset[3] = cpu_to_le32(BD_SBF); } /* clear the STOP BD fetch bit from the first bd of this xfr */ bd = bdi_to_bd(ep, bd_xfr->start_bdi); bd->offset[3] &= cpu_to_le32(~BD_SBF); /* the new eqp will be next hw dqp */ bd_xfr->num_bds = num_bds; bd_xfr->next_hwd_bdi = ep->bd_list.eqp_bdi; /* everything is written correctly before notifying the HW */ wmb(); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja49799.60%150.00%
Dan Carpenter20.40%150.00%
Total499100.00%2100.00%

/* Queue the xfr */
static int bdc_queue_xfr(struct bdc *bdc, struct bdc_req *req) { int num_bds, bd_available; struct bdc_ep *ep; int ret; ep = req->ep; dev_dbg(bdc->dev, "%s req:%p\n", __func__, req); dev_dbg(bdc->dev, "eqp_bdi:%d hwd_bdi:%d\n", ep->bd_list.eqp_bdi, ep->bd_list.hwd_bdi); num_bds = bd_needed_req(req); bd_available = bd_available_ep(ep); /* how many bd's are avaialble on ep */ if (num_bds > bd_available) return -ENOMEM; ret = setup_bd_list_xfr(bdc, req, num_bds); if (ret) return ret; list_add_tail(&req->queue, &ep->queue); bdc_dbg_bd_list(bdc, ep); bdc_notify_xfr(bdc, ep->ep_num); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja144100.00%1100.00%
Total144100.00%1100.00%

/* callback to gadget layer when xfr completes */
static void bdc_req_complete(struct bdc_ep *ep, struct bdc_req *req, int status) { struct bdc *bdc = ep->bdc; if (req == NULL || &req->queue == NULL || &req->usb_req == NULL) return; dev_dbg(bdc->dev, "%s ep:%s status:%d\n", __func__, ep->name, status); list_del(&req->queue); req->usb_req.status = status; usb_gadget_unmap_request(&bdc->gadget, &req->usb_req, ep->dir); if (req->usb_req.complete) { spin_unlock(&bdc->lock); usb_gadget_giveback_request(&ep->usb_ep, &req->usb_req); spin_lock(&bdc->lock); } }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja138100.00%1100.00%
Total138100.00%1100.00%

/* Disable the endpoint */
int bdc_ep_disable(struct bdc_ep *ep) { struct bdc_req *req; struct bdc *bdc; int ret; ret = 0; bdc = ep->bdc; dev_dbg(bdc->dev, "%s() ep->ep_num=%d\n", __func__, ep->ep_num); /* Stop the endpoint */ ret = bdc_stop_ep(bdc, ep->ep_num); /* * Intentionally don't check the ret value of stop, it can fail in * disconnect scenarios, continue with dconfig */ /* de-queue any pending requests */ while (!list_empty(&ep->queue)) { req = list_entry(ep->queue.next, struct bdc_req, queue); bdc_req_complete(ep, req, -ESHUTDOWN); } /* deconfigure the endpoint */ ret = bdc_dconfig_ep(bdc, ep); if (ret) dev_warn(bdc->dev, "dconfig fail but continue with memory free"); ep->flags = 0; /* ep0 memory is not freed, but reused on next connect sr */ if (ep->ep_num == 1) return 0; /* Free the bdl memory */ ep_bd_list_free(ep, ep->bd_list.num_tabs); ep->desc = NULL; ep->comp_desc = NULL; ep->usb_ep.desc = NULL; ep->ep_type = 0; return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja183100.00%1100.00%
Total183100.00%1100.00%

/* Enable the ep */
int bdc_ep_enable(struct bdc_ep *ep) { struct bdc *bdc; int ret = 0; bdc = ep->bdc; dev_dbg(bdc->dev, "%s NUM_TABLES:%d %d\n", __func__, NUM_TABLES, NUM_TABLES_ISOCH); ret = ep_bd_list_alloc(ep); if (ret) { dev_err(bdc->dev, "ep bd list allocation failed:%d\n", ret); return -ENOMEM; } bdc_dbg_bd_list(bdc, ep); /* only for ep0: config ep is called for ep0 from connect event */ ep->flags |= BDC_EP_ENABLED; if (ep->ep_num == 1) return ret; /* Issue a configure endpoint command */ ret = bdc_config_ep(bdc, ep); if (ret) return ret; ep->usb_ep.maxpacket = usb_endpoint_maxp(ep->desc); ep->usb_ep.desc = ep->desc; ep->usb_ep.comp_desc = ep->comp_desc; ep->ep_type = usb_endpoint_type(ep->desc); ep->flags |= BDC_EP_ENABLED; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja164100.00%1100.00%
Total164100.00%1100.00%

/* EP0 related code */ /* Queue a status stage BD */
static int ep0_queue_status_stage(struct bdc *bdc) { struct bdc_req *status_req; struct bdc_ep *ep; status_req = &bdc->status_req; ep = bdc->bdc_ep_array[1]; status_req->ep = ep; status_req->usb_req.length = 0; status_req->usb_req.status = -EINPROGRESS; status_req->usb_req.actual = 0; status_req->usb_req.complete = NULL; bdc_queue_xfr(bdc, status_req); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja86100.00%1100.00%
Total86100.00%1100.00%

/* Queue xfr on ep0 */
static int ep0_queue(struct bdc_ep *ep, struct bdc_req *req) { struct bdc *bdc; int ret; bdc = ep->bdc; dev_dbg(bdc->dev, "%s()\n", __func__); req->usb_req.actual = 0; req->usb_req.status = -EINPROGRESS; req->epnum = ep->ep_num; if (bdc->delayed_status) { bdc->delayed_status = false; /* if status stage was delayed? */ if (bdc->ep0_state == WAIT_FOR_STATUS_START) { /* Queue a status stage BD */ ep0_queue_status_stage(bdc); bdc->ep0_state = WAIT_FOR_STATUS_XMIT; return 0; } } else { /* * if delayed status is false and 0 length transfer is requested * i.e. for status stage of some setup request, then just * return from here the status stage is queued independently */ if (req->usb_req.length == 0) return 0; } ret = usb_gadget_map_request(&bdc->gadget, &req->usb_req, ep->dir); if (ret) { dev_err(bdc->dev, "dma mapping failed %s\n", ep->name); return ret; } return bdc_queue_xfr(bdc, req); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja172100.00%1100.00%
Total172100.00%1100.00%

/* Queue data stage */
static int ep0_queue_data_stage(struct bdc *bdc) { struct bdc_ep *ep; dev_dbg(bdc->dev, "%s\n", __func__); ep = bdc->bdc_ep_array[1]; bdc->ep0_req.ep = ep; bdc->ep0_req.usb_req.complete = NULL; return ep0_queue(ep, &bdc->ep0_req); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja65100.00%1100.00%
Total65100.00%1100.00%

/* Queue req on ep */
static int ep_queue(struct bdc_ep *ep, struct bdc_req *req) { struct bdc *bdc; int ret = 0; if (!req || !ep->usb_ep.desc) return -EINVAL; bdc = ep->bdc; req->usb_req.actual = 0; req->usb_req.status = -EINPROGRESS; req->epnum = ep->ep_num; ret = usb_gadget_map_request(&bdc->gadget, &req->usb_req, ep->dir); if (ret) { dev_err(bdc->dev, "dma mapping failed\n"); return ret; } return bdc_queue_xfr(bdc, req); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja11294.92%150.00%
John W. Linville65.08%150.00%
Total118100.00%2100.00%

/* Dequeue a request from ep */
static int ep_dequeue(struct bdc_ep *ep, struct bdc_req *req) { int start_bdi, end_bdi, tbi, eqp_bdi, curr_hw_dqpi; bool start_pending, end_pending; bool first_remove = false; struct bdc_req *first_req; struct bdc_bd *bd_start; struct bd_table *table; dma_addr_t next_bd_dma; u64 deq_ptr_64 = 0; struct bdc *bdc; u32 tmp_32; int ret; bdc = ep->bdc; start_pending = end_pending = false; eqp_bdi = ep->bd_list.eqp_bdi - 1; if (eqp_bdi < 0) eqp_bdi = ep->bd_list.max_bdi; start_bdi = req->bd_xfr.start_bdi; end_bdi = find_end_bdi(ep, req->bd_xfr.next_hwd_bdi); dev_dbg(bdc->dev, "%s ep:%s start:%d end:%d\n", __func__, ep->name, start_bdi, end_bdi); dev_dbg(bdc->dev, "ep_dequeue ep=%p ep->desc=%p\n", ep, (void *)ep->usb_ep.desc); /* Stop the ep to see where the HW is ? */ ret = bdc_stop_ep(bdc, ep->ep_num); /* if there is an issue with stopping ep, then no need to go further */ if (ret) return 0; /* * After endpoint is stopped, there can be 3 cases, the request * is processed, pending or in the middle of processing */ /* The current hw dequeue pointer */ tmp_32 = bdc_readl(bdc->regs, BDC_EPSTS0(0)); deq_ptr_64 = tmp_32; tmp_32 = bdc_readl(bdc->regs, BDC_EPSTS1(0)); deq_ptr_64 |= ((u64)tmp_32 << 32); /* we have the dma addr of next bd that will be fetched by hardware */ curr_hw_dqpi = bd_add_to_bdi(ep, deq_ptr_64); if (curr_hw_dqpi < 0) return curr_hw_dqpi; /* * curr_hw_dqpi points to actual dqp of HW and HW owns bd's from * curr_hw_dqbdi..eqp_bdi. */ /* Check if start_bdi and end_bdi are in range of HW owned BD's */ if (curr_hw_dqpi > eqp_bdi) { /* there is a wrap from last to 0 */ if (start_bdi >= curr_hw_dqpi || start_bdi <= eqp_bdi) { start_pending = true; end_pending = true; } else if (end_bdi >= curr_hw_dqpi || end_bdi <= eqp_bdi) { end_pending = true; } } else { if (start_bdi >= curr_hw_dqpi) { start_pending = true; end_pending = true; } else if (end_bdi >= curr_hw_dqpi) { end_pending = true; } } dev_dbg(bdc->dev, "start_pending:%d end_pending:%d speed:%d\n", start_pending, end_pending, bdc->gadget.speed); /* If both start till end are processes, we cannot deq req */ if (!start_pending && !end_pending) return -EINVAL; /* * if ep_dequeue is called after disconnect then just return * success from here */ if (bdc->gadget.speed == USB_SPEED_UNKNOWN) return 0; tbi = bdi_to_tbi(ep, req->bd_xfr.next_hwd_bdi); table = ep->bd_list.bd_table_array[tbi]; next_bd_dma = table->dma + sizeof(struct bdc_bd)*(req->bd_xfr.next_hwd_bdi - tbi * ep->bd_list.num_bds_table); first_req = list_first_entry(&ep->queue, struct bdc_req, queue); if (req == first_req) first_remove = true; /* * Due to HW limitation we need to bypadd chain bd's and issue ep_bla, * incase if start is pending this is the first request in the list * then issue ep_bla instead of marking as chain bd */ if (start_pending && !first_remove) { /* * Mark the start bd as Chain bd, and point the chain * bd to next_bd_dma */ bd_start = bdi_to_bd(ep, start_bdi); bd_start->offset[0] = cpu_to_le32(lower_32_bits(next_bd_dma)); bd_start->offset[1] = cpu_to_le32(upper_32_bits(next_bd_dma)); bd_start->offset[2] = 0x0; bd_start->offset[3] = cpu_to_le32(MARK_CHAIN_BD); bdc_dbg_bd_list(bdc, ep); } else if (end_pending) { /* * The transfer is stopped in the middle, move the * HW deq pointer to next_bd_dma */ ret = bdc_ep_bla(bdc, ep, next_bd_dma); if (ret) { dev_err(bdc->dev, "error in ep_bla:%d\n", ret); return ret; } } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja57399.65%150.00%
Al Cooper20.35%150.00%
Total575100.00%2100.00%

/* Halt/Clear the ep based on value */
static int ep_set_halt(struct bdc_ep *ep, u32 value) { struct bdc *bdc; int ret; bdc = ep->bdc; dev_dbg(bdc->dev, "%s ep:%s value=%d\n", __func__, ep->name, value); if (value) { dev_dbg(bdc->dev, "Halt\n"); if (ep->ep_num == 1) bdc->ep0_state = WAIT_FOR_SETUP; ret = bdc_ep_set_stall(bdc, ep->ep_num); if (ret) dev_err(bdc->dev, "failed to set STALL on %s\n", ep->name); else ep->flags |= BDC_EP_STALL; } else { /* Clear */ dev_dbg(bdc->dev, "Before Clear\n"); ret = bdc_ep_clear_stall(bdc, ep->ep_num); if (ret) dev_err(bdc->dev, "failed to clear STALL on %s\n", ep->name); else ep->flags &= ~BDC_EP_STALL; dev_dbg(bdc->dev, "After Clear\n"); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja16898.82%150.00%
Dan Carpenter21.18%150.00%
Total170100.00%2100.00%

/* Free all the ep */
void bdc_free_ep(struct bdc *bdc) { struct bdc_ep *ep; u8 epnum; dev_dbg(bdc->dev, "%s\n", __func__); for (epnum = 1; epnum < bdc->num_eps; epnum++) { ep = bdc->bdc_ep_array[epnum]; if (!ep) continue; if (ep->flags & BDC_EP_ENABLED) ep_bd_list_free(ep, ep->bd_list.num_tabs); /* ep0 is not in this gadget list */ if (epnum != 1) list_del(&ep->usb_ep.ep_list); kfree(ep); } }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja102100.00%1100.00%
Total102100.00%1100.00%

/* USB2 spec, section 7.1.20 */
static int bdc_set_test_mode(struct bdc *bdc) { u32 usb2_pm; usb2_pm = bdc_readl(bdc->regs, BDC_USPPM2); usb2_pm &= ~BDC_PTC_MASK; dev_dbg(bdc->dev, "%s\n", __func__); switch (bdc->test_mode) { case TEST_J: case TEST_K: case TEST_SE0_NAK: case TEST_PACKET: case TEST_FORCE_EN: usb2_pm |= bdc->test_mode << 28; break; default: return -EINVAL; } dev_dbg(bdc->dev, "usb2_pm=%08x", usb2_pm); bdc_writel(bdc->regs, BDC_USPPM2, usb2_pm); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja103100.00%1100.00%
Total103100.00%1100.00%

/* * Helper function to handle Transfer status report with status as either * success or short */
static void handle_xsr_succ_status(struct bdc *bdc, struct bdc_ep *ep, struct bdc_sr *sreport) { int short_bdi, start_bdi, end_bdi, max_len_bds, chain_bds; struct bd_list *bd_list = &ep->bd_list; int actual_length, length_short; struct bd_transfer *bd_xfr; struct bdc_bd *short_bd; struct bdc_req *req; u64 deq_ptr_64 = 0; int status = 0; int sr_status; u32 tmp_32; dev_dbg(bdc->dev, "%s ep:%p\n", __func__, ep); bdc_dbg_srr(bdc, 0); /* do not process thie sr if ignore flag is set */ if (ep->ignore_next_sr) { ep->ignore_next_sr = false; return; } if (unlikely(list_empty(&ep->queue))) { dev_warn(bdc->dev, "xfr srr with no BD's queued\n"); return; } req = list_entry(ep->queue.next, struct bdc_req, queue); bd_xfr = &req->bd_xfr; sr_status = XSF_STS(le32_to_cpu(sreport->offset[3])); /* * sr_status is short and this transfer has more than 1 bd then it needs * special handling, this is only applicable for bulk and ctrl */ if (sr_status == XSF_SHORT && bd_xfr->num_bds > 1) { /* * This is multi bd xfr, lets see which bd * caused short transfer and how many bytes have been * transferred so far. */ tmp_32 = le32_to_cpu(sreport->offset[0]); deq_ptr_64 = tmp_32; tmp_32 = le32_to_cpu(sreport->offset[1]); deq_ptr_64 |= ((u64)tmp_32 << 32); short_bdi = bd_add_to_bdi(ep, deq_ptr_64); if (unlikely(short_bdi < 0)) dev_warn(bdc->dev, "bd doesn't exist?\n"); start_bdi = bd_xfr->start_bdi; /* * We know the start_bdi and short_bdi, how many xfr * bds in between */ if (start_bdi <= short_bdi) { max_len_bds = short_bdi - start_bdi; if (max_len_bds <= bd_list->num_bds_table) { if (!(bdi_to_tbi(ep, start_bdi) == bdi_to_tbi(ep, short_bdi))) max_len_bds--; } else { chain_bds = max_len_bds/bd_list->num_bds_table; max_len_bds -= chain_bds; } } else { /* there is a wrap in the ring within a xfr */ chain_bds = (bd_list->max_bdi - start_bdi)/ bd_list->num_bds_table; chain_bds += short_bdi/bd_list->num_bds_table; max_len_bds = bd_list->max_bdi - start_bdi; max_len_bds += short_bdi; max_len_bds -= chain_bds; } /* max_len_bds is the number of full length bds */ end_bdi = find_end_bdi(ep, bd_xfr->next_hwd_bdi); if (!(end_bdi == short_bdi)) ep->ignore_next_sr = true; actual_length = max_len_bds * BD_MAX_BUFF_SIZE; short_bd = bdi_to_bd(ep, short_bdi); /* length queued */ length_short = le32_to_cpu(short_bd->offset[2]) & 0x1FFFFF; /* actual length trensfered */ length_short -= SR_BD_LEN(le32_to_cpu(sreport->offset[2])); actual_length += length_short; req->usb_req.actual = actual_length; } else { req->usb_req.actual = req->usb_req.length - SR_BD_LEN(le32_to_cpu(sreport->offset[2])); dev_dbg(bdc->dev, "len=%d actual=%d bd_xfr->next_hwd_bdi:%d\n", req->usb_req.length, req->usb_req.actual, bd_xfr->next_hwd_bdi); } /* Update the dequeue pointer */ ep->bd_list.hwd_bdi = bd_xfr->next_hwd_bdi; if (req->usb_req.actual < req->usb_req.length) { dev_dbg(bdc->dev, "short xfr on %d\n", ep->ep_num); if (req->usb_req.short_not_ok) status = -EREMOTEIO; } bdc_req_complete(ep, bd_xfr->req, status); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja571100.00%1100.00%
Total571100.00%1100.00%

/* EP0 setup related packet handlers */ /* * Setup packet received, just store the packet and process on next DS or SS * started SR */
void bdc_xsf_ep0_setup_recv(struct bdc *bdc, struct bdc_sr *sreport) { struct usb_ctrlrequest *setup_pkt; u32 len; dev_dbg(bdc->dev, "%s ep0_state:%s\n", __func__, ep0_state_string[bdc->ep0_state]); /* Store received setup packet */ setup_pkt = &bdc->setup_pkt; memcpy(setup_pkt, &sreport->offset[0], sizeof(*setup_pkt)); len = le16_to_cpu(setup_pkt->wLength); if (!len) bdc->ep0_state = WAIT_FOR_STATUS_START; else bdc->ep0_state = WAIT_FOR_DATA_START; dev_dbg(bdc->dev, "%s exit ep0_state:%s\n", __func__, ep0_state_string[bdc->ep0_state]); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja113100.00%1100.00%
Total113100.00%1100.00%

/* Stall ep0 */
static void ep0_stall(struct bdc *bdc) { struct bdc_ep *ep = bdc->bdc_ep_array[1]; struct bdc_req *req; dev_dbg(bdc->dev, "%s\n", __func__); bdc->delayed_status = false; ep_set_halt(ep, 1); /* de-queue any pendig requests */ while (!list_empty(&ep->queue)) { req = list_entry(ep->queue.next, struct bdc_req, queue); bdc_req_complete(ep, req, -ESHUTDOWN); } }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja92100.00%1100.00%
Total92100.00%1100.00%

/* SET_ADD handlers */
static int ep0_set_address(struct bdc *bdc, struct usb_ctrlrequest *ctrl) { enum usb_device_state state = bdc->gadget.state; int ret = 0; u32 addr; addr = le16_to_cpu(ctrl->wValue); dev_dbg(bdc->dev, "%s addr:%d dev state:%d\n", __func__, addr, state); if (addr > 127) return -EINVAL; switch (state) { case USB_STATE_DEFAULT: case USB_STATE_ADDRESS: /* Issue Address device command */ ret = bdc_address_device(bdc, addr); if (ret) return ret; if (addr) usb_gadget_set_state(&bdc->gadget, USB_STATE_ADDRESS); else usb_gadget_set_state(&bdc->gadget, USB_STATE_DEFAULT); bdc->dev_addr = addr; break; default: dev_warn(bdc->dev, "SET Address in wrong device state %d\n", state); ret = -EINVAL; } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja149100.00%1100.00%
Total149100.00%1100.00%

/* Handler for SET/CLEAR FEATURE requests for device */
static int ep0_handle_feature_dev(struct bdc *bdc, u16 wValue, u16 wIndex, bool set) { enum usb_device_state state = bdc->gadget.state; u32 usppms = 0; dev_dbg(bdc->dev, "%s set:%d dev state:%d\n", __func__, set, state); switch (wValue) { case USB_DEVICE_REMOTE_WAKEUP: dev_dbg(bdc->dev, "USB_DEVICE_REMOTE_WAKEUP\n"); if (set) bdc->devstatus |= REMOTE_WAKE_ENABLE; else bdc->devstatus &= ~REMOTE_WAKE_ENABLE; break; case USB_DEVICE_TEST_MODE: dev_dbg(bdc->dev, "USB_DEVICE_TEST_MODE\n"); if ((wIndex & 0xFF) || (bdc->gadget.speed != USB_SPEED_HIGH) || !set) return -EINVAL; bdc->test_mode = wIndex >> 8; break; case USB_DEVICE_U1_ENABLE: dev_dbg(bdc->dev, "USB_DEVICE_U1_ENABLE\n"); if (bdc->gadget.speed != USB_SPEED_SUPER || state != USB_STATE_CONFIGURED) return -EINVAL; usppms = bdc_readl(bdc->regs, BDC_USPPMS); if (set) { /* clear previous u1t */ usppms &= ~BDC_U1T(BDC_U1T_MASK); usppms |= BDC_U1T(U1_TIMEOUT); usppms |= BDC_U1E | BDC_PORT_W1S; bdc->devstatus |= (1 << USB_DEV_STAT_U1_ENABLED); } else { usppms &= ~BDC_U1E; usppms |= BDC_PORT_W1S; bdc->devstatus &= ~(1 << USB_DEV_STAT_U1_ENABLED); } bdc_writel(bdc->regs, BDC_USPPMS, usppms); break; case USB_DEVICE_U2_ENABLE: dev_dbg(bdc->dev, "USB_DEVICE_U2_ENABLE\n"); if (bdc->gadget.speed != USB_SPEED_SUPER || state != USB_STATE_CONFIGURED) return -EINVAL; usppms = bdc_readl(bdc->regs, BDC_USPPMS); if (set) { usppms |= BDC_U2E; usppms |= BDC_U2A; bdc->devstatus |= (1 << USB_DEV_STAT_U2_ENABLED); } else { usppms &= ~BDC_U2E; usppms &= ~BDC_U2A; bdc->devstatus &= ~(1 << USB_DEV_STAT_U2_ENABLED); } bdc_writel(bdc->regs, BDC_USPPMS, usppms); break; case USB_DEVICE_LTM_ENABLE: dev_dbg(bdc->dev, "USB_DEVICE_LTM_ENABLE?\n"); if (bdc->gadget.speed != USB_SPEED_SUPER || state != USB_STATE_CONFIGURED) return -EINVAL; break; default: dev_err(bdc->dev, "Unknown wValue:%d\n", wValue); return -EOPNOTSUPP; } /* USB_RECIP_DEVICE end */ return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja399100.00%1100.00%
Total399100.00%1100.00%

/* SET/CLEAR FEATURE handler */
static int ep0_handle_feature(struct bdc *bdc, struct usb_ctrlrequest *setup_pkt, bool set) { enum usb_device_state state = bdc->gadget.state; struct bdc_ep *ep; u16 wValue; u16 wIndex; int epnum; wValue = le16_to_cpu(setup_pkt->wValue); wIndex = le16_to_cpu(setup_pkt->wIndex); dev_dbg(bdc->dev, "%s wValue=%d wIndex=%d devstate=%08x speed=%d set=%d", __func__, wValue, wIndex, state, bdc->gadget.speed, set); switch (setup_pkt->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: return ep0_handle_feature_dev(bdc, wValue, wIndex, set); case USB_RECIP_INTERFACE: dev_dbg(bdc->dev, "USB_RECIP_INTERFACE\n"); /* USB3 spec, sec 9.4.9 */ if (wValue != USB_INTRF_FUNC_SUSPEND) return -EINVAL; /* USB3 spec, Table 9-8 */ if (set) { if (wIndex & USB_INTRF_FUNC_SUSPEND_RW) { dev_dbg(bdc->dev, "SET REMOTE_WAKEUP\n"); bdc->devstatus |= REMOTE_WAKE_ENABLE; } else { dev_dbg(bdc->dev, "CLEAR REMOTE_WAKEUP\n"); bdc->devstatus &= ~REMOTE_WAKE_ENABLE; } } break; case USB_RECIP_ENDPOINT: dev_dbg(bdc->dev, "USB_RECIP_ENDPOINT\n"); if (wValue != USB_ENDPOINT_HALT) return -EINVAL; epnum = wIndex & USB_ENDPOINT_NUMBER_MASK; if (epnum) { if ((wIndex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) epnum = epnum * 2 + 1; else epnum *= 2; } else { epnum = 1; /*EP0*/ } /* * If CLEAR_FEATURE on ep0 then don't do anything as the stall * condition on ep0 has already been cleared when SETUP packet * was received. */ if (epnum == 1 && !set) { dev_dbg(bdc->dev, "ep0 stall already cleared\n"); return 0; } dev_dbg(bdc->dev, "epnum=%d\n", epnum); ep = bdc->bdc_ep_array[epnum]; if (!ep) return -EINVAL; return ep_set_halt(ep, set); default: dev_err(bdc->dev, "Unknown recipient\n"); return -EINVAL; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja327100.00%1100.00%
Total327100.00%1100.00%

/* GET_STATUS request handler */
static int ep0_handle_status(struct bdc *bdc, struct usb_ctrlrequest *setup_pkt) { enum usb_device_state state = bdc->gadget.state; struct bdc_ep *ep; u16 usb_status = 0; u32 epnum; u16 wIndex; /* USB2.0 spec sec 9.4.5 */ if (state == USB_STATE_DEFAULT) return -EINVAL; wIndex = le16_to_cpu(setup_pkt->wIndex); dev_dbg(bdc->dev, "%s\n", __func__); usb_status = bdc->devstatus; switch (setup_pkt->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: dev_dbg(bdc->dev, "USB_RECIP_DEVICE devstatus:%08x\n", bdc->devstatus); /* USB3 spec, sec 9.4.5 */ if (bdc->gadget.speed == USB_SPEED_SUPER) usb_status &= ~REMOTE_WAKE_ENABLE; break; case USB_RECIP_INTERFACE: dev_dbg(bdc->dev, "USB_RECIP_INTERFACE\n"); if (bdc->gadget.speed == USB_SPEED_SUPER) { /* * This should come from func for Func remote wkup * usb_status |=1; */ if (bdc->devstatus & REMOTE_WAKE_ENABLE) usb_status |= REMOTE_WAKE_ENABLE; } else { usb_status = 0; } break; case USB_RECIP_ENDPOINT: dev_dbg(bdc->dev, "USB_RECIP_ENDPOINT\n"); epnum = wIndex & USB_ENDPOINT_NUMBER_MASK; if (epnum) { if ((wIndex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) epnum = epnum*2 + 1; else epnum *= 2; } else { epnum = 1; /* EP0 */ } ep = bdc->bdc_ep_array[epnum]; if (!ep) { dev_err(bdc->dev, "ISSUE, GET_STATUS for invalid EP ?"); return -EINVAL; } if (ep->flags & BDC_EP_STALL) usb_status |= 1 << USB_ENDPOINT_HALT; break; default: dev_err(bdc->dev, "Unknown recipient for get_status\n"); return -EINVAL; } /* prepare a data stage for GET_STATUS */ dev_dbg(bdc->dev, "usb_status=%08x\n", usb_status); *(__le16 *)bdc->ep0_response_buff = cpu_to_le16(usb_status); bdc->ep0_req.usb_req.length = 2; bdc->ep0_req.usb_req.buf = &bdc->ep0_response_buff; ep0_queue_data_stage(bdc); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja337100.00%1100.00%
Total337100.00%1100.00%


static void ep0_set_sel_cmpl(struct usb_ep *_ep, struct usb_request *_req) { /* ep0_set_sel_cmpl */ }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja17100.00%1100.00%
Total17100.00%1100.00%

/* Queue data stage to handle 6 byte SET_SEL request */
static int ep0_set_sel(struct bdc *bdc, struct usb_ctrlrequest *setup_pkt) { struct bdc_ep *ep; u16 wLength; dev_dbg(bdc->dev, "%s\n", __func__); wLength = le16_to_cpu(setup_pkt->wLength); if (unlikely(wLength != 6)) { dev_err(bdc->dev, "%s Wrong wLength:%d\n", __func__, wLength); return -EINVAL; } ep = bdc->bdc_ep_array[1]; bdc->ep0_req.ep = ep; bdc->ep0_req.usb_req.length = 6; bdc->ep0_req.usb_req.buf = bdc->ep0_response_buff; bdc->ep0_req.usb_req.complete = ep0_set_sel_cmpl; ep0_queue_data_stage(bdc); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja129100.00%1100.00%
Total129100.00%1100.00%

/* * Queue a 0 byte bd only if wLength is more than the length and and length is * a multiple of MaxPacket then queue 0 byte BD */
static int ep0_queue_zlp(struct bdc *bdc) { int ret; dev_dbg(bdc->dev, "%s\n", __func__); bdc->ep0_req.ep = bdc->bdc_ep_array[1]; bdc->ep0_req.usb_req.length = 0; bdc->ep0_req.usb_req.complete = NULL; bdc->ep0_state = WAIT_FOR_DATA_START; ret = bdc_queue_xfr(bdc, &bdc->ep0_req); if (ret) { dev_err(bdc->dev, "err queueing zlp :%d\n", ret); return ret; } bdc->ep0_state = WAIT_FOR_DATA_XMIT; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja105100.00%1100.00%
Total105100.00%1100.00%

/* Control request handler */
static int handle_control_request(struct bdc *bdc) { enum usb_device_state state = bdc->gadget.state; struct usb_ctrlrequest *setup_pkt; int delegate_setup = 0; int ret = 0; int config = 0; setup_pkt = &bdc->setup_pkt; dev_dbg(bdc->dev, "%s\n", __func__); if ((setup_pkt->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (setup_pkt->bRequest) { case USB_REQ_SET_ADDRESS: dev_dbg(bdc->dev, "USB_REQ_SET_ADDRESS\n"); ret = ep0_set_address(bdc, setup_pkt); bdc->devstatus &= DEVSTATUS_CLEAR; break; case USB_REQ_SET_CONFIGURATION: dev_dbg(bdc->dev, "USB_REQ_SET_CONFIGURATION\n"); if (state == USB_STATE_ADDRESS) { usb_gadget_set_state(&bdc->gadget, USB_STATE_CONFIGURED); } else if (state == USB_STATE_CONFIGURED) { /* * USB2 spec sec 9.4.7, if wValue is 0 then dev * is moved to addressed state */ config = le16_to_cpu(setup_pkt->wValue); if (!config) usb_gadget_set_state( &bdc->gadget, USB_STATE_ADDRESS); } delegate_setup = 1; break; case USB_REQ_SET_FEATURE: dev_dbg(bdc->dev, "USB_REQ_SET_FEATURE\n"); ret = ep0_handle_feature(bdc, setup_pkt, 1); break; case USB_REQ_CLEAR_FEATURE: dev_dbg(bdc->dev, "USB_REQ_CLEAR_FEATURE\n"); ret = ep0_handle_feature(bdc, setup_pkt, 0); break; case USB_REQ_GET_STATUS: dev_dbg(bdc->dev, "USB_REQ_GET_STATUS\n"); ret = ep0_handle_status(bdc, setup_pkt); break; case USB_REQ_SET_SEL: dev_dbg(bdc->dev, "USB_REQ_SET_SEL\n"); ret = ep0_set_sel(bdc, setup_pkt); break; case USB_REQ_SET_ISOCH_DELAY: dev_warn(bdc->dev, "USB_REQ_SET_ISOCH_DELAY not handled\n"); ret = 0; break; default: delegate_setup = 1; } } else { delegate_setup = 1; } if (delegate_setup) { spin_unlock(&bdc->lock); ret = bdc->gadget_driver->setup(&bdc->gadget, setup_pkt); spin_lock(&bdc->lock); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja340100.00%1100.00%
Total340100.00%1100.00%

/* EP0: Data stage started */
void bdc_xsf_ep0_data_start(struct bdc *bdc, struct bdc_sr *sreport) { struct bdc_ep *ep; int ret = 0; dev_dbg(bdc->dev, "%s\n", __func__); ep = bdc->bdc_ep_array[1]; /* If ep0 was stalled, the clear it first */ if (ep->flags & BDC_EP_STALL) { ret = ep_set_halt(ep, 0); if (ret) goto err; } if (bdc->ep0_state != WAIT_FOR_DATA_START) dev_warn(bdc->dev, "Data stage not expected ep0_state:%s\n", ep0_state_string[bdc->ep0_state]); ret = handle_control_request(bdc); if (ret == USB_GADGET_DELAYED_STATUS) { /* * The ep0 state will remain WAIT_FOR_DATA_START till * we received ep_queue on ep0 */ bdc->delayed_status = true; return; } if (!ret) { bdc->ep0_state = WAIT_FOR_DATA_XMIT; dev_dbg(bdc->dev, "ep0_state:%s", ep0_state_string[bdc->ep0_state]); return; } err: ep0_stall(bdc); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja156100.00%1100.00%
Total156100.00%1100.00%

/* EP0: status stage started */
void bdc_xsf_ep0_status_start(struct bdc *bdc, struct bdc_sr *sreport) { struct usb_ctrlrequest *setup_pkt; struct bdc_ep *ep; int ret = 0; dev_dbg(bdc->dev, "%s ep0_state:%s", __func__, ep0_state_string[bdc->ep0_state]); ep = bdc->bdc_ep_array[1]; /* check if ZLP was queued? */ if (bdc->zlp_needed) bdc->zlp_needed = false; if (ep->flags & BDC_EP_STALL) { ret = ep_set_halt(ep, 0); if (ret) goto err; } if ((bdc->ep0_state != WAIT_FOR_STATUS_START) && (bdc->ep0_state != WAIT_FOR_DATA_XMIT)) dev_err(bdc->dev, "Status stage recv but ep0_state:%s\n", ep0_state_string[bdc->ep0_state]); /* check if data stage is in progress ? */ if (bdc->ep0_state == WAIT_FOR_DATA_XMIT) { bdc->ep0_state = STATUS_PENDING; /* Status stage will be queued upon Data stage transmit event */ dev_dbg(bdc->dev, "status started but data not transmitted yet\n"); return; } setup_pkt = &bdc->setup_pkt; /* * 2 stage setup then only process the setup, for 3 stage setup the date * stage is already handled */ if (!le16_to_cpu(setup_pkt->wLength)) { ret = handle_control_request(bdc); if (ret == USB_GADGET_DELAYED_STATUS) { bdc->delayed_status = true; /* ep0_state will remain WAIT_FOR_STATUS_START */ return; } } if (!ret) { /* Queue a status stage BD */ ep0_queue_status_stage(bdc); bdc->ep0_state = WAIT_FOR_STATUS_XMIT; dev_dbg(bdc->dev, "ep0_state:%s", ep0_state_string[bdc->ep0_state]); return; } err: ep0_stall(bdc); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja244100.00%1100.00%
Total244100.00%1100.00%

/* Helper function to update ep0 upon SR with xsf_succ or xsf_short */
static void ep0_xsf_complete(struct bdc *bdc, struct bdc_sr *sreport) { dev_dbg(bdc->dev, "%s\n", __func__); switch (bdc->ep0_state) { case WAIT_FOR_DATA_XMIT: bdc->ep0_state = WAIT_FOR_STATUS_START; break; case WAIT_FOR_STATUS_XMIT: bdc->ep0_state = WAIT_FOR_SETUP; if (bdc->test_mode) { int ret; dev_dbg(bdc->dev, "test_mode:%d\n", bdc->test_mode); ret = bdc_set_test_mode(bdc); if (ret < 0) { dev_err(bdc->dev, "Err in setting Test mode\n"); return; } bdc->test_mode = 0; } break; case STATUS_PENDING: bdc_xsf_ep0_status_start(bdc, sreport); break; default: dev_err(bdc->dev, "Unknown ep0_state:%s\n", ep0_state_string[bdc->ep0_state]); } }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja138100.00%1100.00%
Total138100.00%1100.00%

/* xfr completion status report handler */
void bdc_sr_xsf(struct bdc *bdc, struct bdc_sr *sreport) { struct bdc_ep *ep; u32 sr_status; u8 ep_num; ep_num = (le32_to_cpu(sreport->offset[3])>>4) & 0x1f; ep = bdc->bdc_ep_array[ep_num]; if (!ep || !(ep->flags & BDC_EP_ENABLED)) { dev_err(bdc->dev, "xsf for ep not enabled\n"); return; } /* * check if this transfer is after link went from U3->U0 due * to remote wakeup */ if (bdc->devstatus & FUNC_WAKE_ISSUED) { bdc->devstatus &= ~(FUNC_WAKE_ISSUED); dev_dbg(bdc->dev, "%s clearing FUNC_WAKE_ISSUED flag\n", __func__); } sr_status = XSF_STS(le32_to_cpu(sreport->offset[3])); dev_dbg_ratelimited(bdc->dev, "%s sr_status=%d ep:%s\n", __func__, sr_status, ep->name); switch (sr_status) { case XSF_SUCC: case XSF_SHORT: handle_xsr_succ_status(bdc, ep, sreport); if (ep_num == 1) ep0_xsf_complete(bdc, sreport); break; case XSF_SETUP_RECV: case XSF_DATA_START: case XSF_STATUS_START: if (ep_num != 1) { dev_err(bdc->dev, "ep0 related packets on non ep0 endpoint"); return; } bdc->sr_xsf_ep0[sr_status - XSF_SETUP_RECV](bdc, sreport); break; case XSF_BABB: if (ep_num == 1) { dev_dbg(bdc->dev, "Babble on ep0 zlp_need:%d\n", bdc->zlp_needed); /* * If the last completed transfer had wLength >Data Len, * and Len is multiple of MaxPacket,then queue ZLP */ if (bdc->zlp_needed) { /* queue 0 length bd */ ep0_queue_zlp(bdc); return; } } dev_warn(bdc->dev, "Babble on ep not handled\n"); break; default: dev_warn(bdc->dev, "sr status not handled:%x\n", sr_status); break; } }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja282100.00%1100.00%
Total282100.00%1100.00%


static int bdc_gadget_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) { struct bdc_req *req; unsigned long flags; struct bdc_ep *ep; struct bdc *bdc; int ret; if (!_ep || !_ep->desc) return -ESHUTDOWN; if (!_req || !_req->complete || !_req->buf) return -EINVAL; ep = to_bdc_ep(_ep); req = to_bdc_req(_req); bdc = ep->bdc; dev_dbg(bdc->dev, "%s ep:%p req:%p\n", __func__, ep, req); dev_dbg(bdc->dev, "queuing request %p to %s length %d zero:%d\n", _req, ep->name, _req->length, _req->zero); if (!ep->usb_ep.desc) { dev_warn(bdc->dev, "trying to queue req %p to disabled %s\n", _req, ep->name); return -ESHUTDOWN; } if (_req->length > MAX_XFR_LEN) { dev_warn(bdc->dev, "req length > supported MAX:%d requested:%d\n", MAX_XFR_LEN, _req->length); return -EOPNOTSUPP; } spin_lock_irqsave(&bdc->lock, flags); if (ep == bdc->bdc_ep_array[1]) ret = ep0_queue(ep, req); else ret = ep_queue(ep, req); spin_unlock_irqrestore(&bdc->lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja244100.00%1100.00%
Total244100.00%1100.00%


static int bdc_gadget_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) { struct bdc_req *req; unsigned long flags; struct bdc_ep *ep; struct bdc *bdc; int ret; if (!_ep || !_req) return -EINVAL; ep = to_bdc_ep(_ep); req = to_bdc_req(_req); bdc = ep->bdc; dev_dbg(bdc->dev, "%s ep:%s req:%p\n", __func__, ep->name, req); bdc_dbg_bd_list(bdc, ep); spin_lock_irqsave(&bdc->lock, flags); /* make sure it's still queued on this endpoint */ list_for_each_entry(req, &ep->queue, queue) { if (&req->usb_req == _req) break; } if (&req->usb_req != _req) { spin_unlock_irqrestore(&bdc->lock, flags); dev_err(bdc->dev, "usb_req !=req n"); return -EINVAL; } ret = ep_dequeue(ep, req); if (ret) { ret = -EOPNOTSUPP; goto err; } bdc_req_complete(ep, req, -ECONNRESET); err: bdc_dbg_bd_list(bdc, ep); spin_unlock_irqrestore(&bdc->lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja214100.00%1100.00%
Total214100.00%1100.00%


static int bdc_gadget_ep_set_halt(struct usb_ep *_ep, int value) { unsigned long flags; struct bdc_ep *ep; struct bdc *bdc; int ret; ep = to_bdc_ep(_ep); bdc = ep->bdc; dev_dbg(bdc->dev, "%s ep:%s value=%d\n", __func__, ep->name, value); spin_lock_irqsave(&bdc->lock, flags); if (usb_endpoint_xfer_isoc(ep->usb_ep.desc)) ret = -EINVAL; else if (!list_empty(&ep->queue)) ret = -EAGAIN; else ret = ep_set_halt(ep, value); spin_unlock_irqrestore(&bdc->lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja127100.00%1100.00%
Total127100.00%1100.00%


static struct usb_request *bdc_gadget_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) { struct bdc_req *req; struct bdc_ep *ep; req = kzalloc(sizeof(*req), gfp_flags); if (!req) return NULL; ep = to_bdc_ep(_ep); req->ep = ep; req->epnum = ep->ep_num; req->usb_req.dma = DMA_ADDR_INVALID; dev_dbg(ep->bdc->dev, "%s ep:%s req:%p\n", __func__, ep->name, req); return &req->usb_req; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja101100.00%1100.00%
Total101100.00%1100.00%


static void bdc_gadget_free_request(struct usb_ep *_ep, struct usb_request *_req) { struct bdc_req *req; req = to_bdc_req(_req); kfree(req); }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja33100.00%1100.00%
Total33100.00%1100.00%

/* endpoint operations */ /* configure endpoint and also allocate resources */
static int bdc_gadget_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) { unsigned long flags; struct bdc_ep *ep; struct bdc *bdc; int ret; if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { pr_debug("bdc_gadget_ep_enable invalid parameters\n"); return -EINVAL; } if (!desc->wMaxPacketSize) { pr_debug("bdc_gadget_ep_enable missing wMaxPacketSize\n"); return -EINVAL; } ep = to_bdc_ep(_ep); bdc = ep->bdc; /* Sanity check, upper layer will not send enable for ep0 */ if (ep == bdc->bdc_ep_array[1]) return -EINVAL; if (!bdc->gadget_driver || bdc->gadget.speed == USB_SPEED_UNKNOWN) { return -ESHUTDOWN; } dev_dbg(bdc->dev, "%s Enabling %s\n", __func__, ep->name); spin_lock_irqsave(&bdc->lock, flags); ep->desc = desc; ep->comp_desc = _ep->comp_desc; ret = bdc_ep_enable(ep); spin_unlock_irqrestore(&bdc->lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja186100.00%1100.00%
Total186100.00%1100.00%


static int bdc_gadget_ep_disable(struct usb_ep *_ep) { unsigned long flags; struct bdc_ep *ep; struct bdc *bdc; int ret; if (!_ep) { pr_debug("bdc: invalid parameters\n"); return -EINVAL; } ep = to_bdc_ep(_ep); bdc = ep->bdc; /* Upper layer will not call this for ep0, but do a sanity check */ if (ep == bdc->bdc_ep_array[1]) { dev_warn(bdc->dev, "%s called for ep0\n", __func__); return -EINVAL; } dev_dbg(bdc->dev, "%s() ep:%s ep->flags:%08x\n", __func__, ep->name, ep->flags); if (!(ep->flags & BDC_EP_ENABLED)) { dev_warn(bdc->dev, "%s is already disabled\n", ep->name); return 0; } spin_lock_irqsave(&bdc->lock, flags); ret = bdc_ep_disable(ep); spin_unlock_irqrestore(&bdc->lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja164100.00%1100.00%
Total164100.00%1100.00%

static const struct usb_ep_ops bdc_gadget_ep_ops = { .enable = bdc_gadget_ep_enable, .disable = bdc_gadget_ep_disable, .alloc_request = bdc_gadget_alloc_request, .free_request = bdc_gadget_free_request, .queue = bdc_gadget_ep_queue, .dequeue = bdc_gadget_ep_dequeue, .set_halt = bdc_gadget_ep_set_halt }; /* dir = 1 is IN */
static int init_ep(struct bdc *bdc, u32 epnum, u32 dir) { struct bdc_ep *ep; dev_dbg(bdc->dev, "%s epnum=%d dir=%d\n", __func__, epnum, dir); ep = kzalloc(sizeof(*ep), GFP_KERNEL); if (!ep) return -ENOMEM; ep->bdc = bdc; ep->dir = dir; if (dir) ep->usb_ep.caps.dir_in = true; else ep->usb_ep.caps.dir_out = true; /* ep->ep_num is the index inside bdc_ep */ if (epnum == 1) { ep->ep_num = 1; bdc->bdc_ep_array[ep->ep_num] = ep; snprintf(ep->name, sizeof(ep->name), "ep%d", epnum - 1); usb_ep_set_maxpacket_limit(&ep->usb_ep, EP0_MAX_PKT_SIZE); ep->usb_ep.caps.type_control = true; ep->comp_desc = NULL; bdc->gadget.ep0 = &ep->usb_ep; } else { if (dir) ep->ep_num = epnum * 2 - 1; else ep->ep_num = epnum * 2 - 2; bdc->bdc_ep_array[ep->ep_num] = ep; snprintf(ep->name, sizeof(ep->name), "ep%d%s", epnum - 1, dir & 1 ? "in" : "out"); usb_ep_set_maxpacket_limit(&ep->usb_ep, 1024); ep->usb_ep.caps.type_iso = true; ep->usb_ep.caps.type_bulk = true; ep->usb_ep.caps.type_int = true; ep->usb_ep.max_streams = 0; list_add_tail(&ep->usb_ep.ep_list, &bdc->gadget.ep_list); } ep->usb_ep.ops = &bdc_gadget_ep_ops; ep->usb_ep.name = ep->name; ep->flags = 0; ep->ignore_next_sr = false; dev_dbg(bdc->dev, "ep=%p ep->usb_ep.name=%s epnum=%d ep->epnum=%d\n", ep, ep->usb_ep.name, epnum, ep->ep_num); INIT_LIST_HEAD(&ep->queue); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja31182.71%150.00%
Robert Baldyga6517.29%150.00%
Total376100.00%2100.00%

/* Init all ep */
int bdc_init_ep(struct bdc *bdc) { u8 epnum; int ret; dev_dbg(bdc->dev, "%s()\n", __func__); INIT_LIST_HEAD(&bdc->gadget.ep_list); /* init ep0 */ ret = init_ep(bdc, 1, 0); if (ret) { dev_err(bdc->dev, "init ep ep0 fail %d\n", ret); return ret; } for (epnum = 2; epnum <= bdc->num_eps / 2; epnum++) { /* OUT */ ret = init_ep(bdc, epnum, 0); if (ret) { dev_err(bdc->dev, "init ep failed for:%d error: %d\n", epnum, ret); return ret; } /* IN */ ret = init_ep(bdc, epnum, 1); if (ret) { dev_err(bdc->dev, "init ep failed for:%d error: %d\n", epnum, ret); return ret; } } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja159100.00%1100.00%
Total159100.00%1100.00%


Overall Contributors

PersonTokensPropCommitsCommitProp
Ashwini Pahuja937299.10%112.50%
Robert Baldyga650.69%112.50%
Sudip Mukherjee70.07%112.50%
John W. Linville60.06%112.50%
Dan Carpenter40.04%225.00%
Al Cooper20.02%112.50%
Colin Ian King10.01%112.50%
Total9457100.00%8100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.