summaryrefslogtreecommitdiff
path: root/target/linux/cns3xxx
diff options
context:
space:
mode:
authorkaloz <kaloz@3c298f89-4303-0410-b956-a3cf2f4a3e73>2012-09-28 17:31:22 +0000
committerkaloz <kaloz@3c298f89-4303-0410-b956-a3cf2f4a3e73>2012-09-28 17:31:22 +0000
commit07511ec7935d345aed19af83d6bf80fda53c806e (patch)
treea0e88c8efaa7ff2227b0f80d4e4793ca3de7765e /target/linux/cns3xxx
parent98353f24a214581052633f0557cad8d97f9828b0 (diff)
USB iso mode fixes
Resolves an issue where isochronouse USB would cause the driver to hang as well as scheduling issues. Signed-off-by: Tim Harvey <tharvey@gateworks.com> git-svn-id: svn://svn.openwrt.org/openwrt/trunk@33579 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/cns3xxx')
-rw-r--r--target/linux/cns3xxx/patches-3.3/200-dwc_otg.patch363
1 files changed, 234 insertions, 129 deletions
diff --git a/target/linux/cns3xxx/patches-3.3/200-dwc_otg.patch b/target/linux/cns3xxx/patches-3.3/200-dwc_otg.patch
index 8439d0ec70..f0510fae2c 100644
--- a/target/linux/cns3xxx/patches-3.3/200-dwc_otg.patch
+++ b/target/linux/cns3xxx/patches-3.3/200-dwc_otg.patch
@@ -7887,7 +7887,7 @@
+#endif
--- /dev/null
+++ b/drivers/usb/dwc/otg_hcd.c
-@@ -0,0 +1,2735 @@
+@@ -0,0 +1,2752 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.c $
+ * $Revision: #75 $
@@ -8056,7 +8056,9 @@
+ dwc_otg_qh_t *qh;
+ struct list_head *qtd_item;
+ dwc_otg_qtd_t *qtd;
++ unsigned long flags;
+
++ SPIN_LOCK_IRQSAVE(&hcd->lock, flags);
+ list_for_each(qh_item, qh_list) {
+ qh = list_entry(qh_item, dwc_otg_qh_t, qh_list_entry);
+ for (qtd_item = qh->qtd_list.next;
@@ -8070,6 +8072,7 @@
+ dwc_otg_hcd_qtd_remove_and_free(hcd, qtd);
+ }
+ }
++ SPIN_UNLOCK_IRQRESTORE(&hcd->lock, flags);
+}
+
+/**
@@ -8313,10 +8316,14 @@
+ hcd->regs = otg_dev->base;
+ hcd->self.otg_port = 1;
+
++ /* Integrate TT in root hub, by default this is disbled. */
++ hcd->has_tt = 1;
++
+ /* Initialize the DWC OTG HCD. */
+ dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+ dwc_otg_hcd->core_if = otg_dev->core_if;
+ otg_dev->hcd = dwc_otg_hcd;
++ init_hcd_usecs(dwc_otg_hcd);
+
+ /* */
+ spin_lock_init(&dwc_otg_hcd->lock);
@@ -8534,6 +8541,7 @@
+{
+ struct list_head *item;
+ dwc_otg_qh_t *qh;
++ unsigned long flags;
+
+ if (!qh_list->next) {
+ /* The list hasn't been initialized yet. */
@@ -8543,10 +8551,12 @@
+ /* Ensure there are no QTDs or URBs left. */
+ kill_urbs_in_qh_list(hcd, qh_list);
+
++ SPIN_LOCK_IRQSAVE(&hcd->lock, flags);
+ for (item = qh_list->next; item != qh_list; item = qh_list->next) {
+ qh = list_entry(item, dwc_otg_qh_t, qh_list_entry);
+ dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+ }
++ SPIN_UNLOCK_IRQRESTORE(&hcd->lock, flags);
+}
+
+/**
@@ -8838,6 +8848,10 @@
+ urb_qtd = (dwc_otg_qtd_t *)urb->hcpriv;
+ qh = (dwc_otg_qh_t *)ep->hcpriv;
+
++ if (urb_qtd == NULL) {
++ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
++ return 0;
++ }
+#ifdef DEBUG
+ if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+ dump_urb_info(urb, "dwc_otg_hcd_urb_dequeue");
@@ -8869,15 +8883,17 @@
+ */
+ dwc_otg_hcd_qtd_remove_and_free(dwc_otg_hcd, urb_qtd);
+ if (urb_qtd == qh->qtd_in_process) {
++ /* Note that dwc_otg_hcd_qh_deactivate() locks the spin_lock again */
++ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
+ dwc_otg_hcd_qh_deactivate(dwc_otg_hcd, qh, 0);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
-+ } else if (list_empty(&qh->qtd_list)) {
-+ dwc_otg_hcd_qh_remove(dwc_otg_hcd, qh);
++ } else {
++ if (list_empty(&qh->qtd_list))
++ dwc_otg_hcd_qh_remove(dwc_otg_hcd, qh);
++ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
+ }
+
-+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
-+
+ urb->hcpriv = NULL;
+
+ /* Higher layer software sets URB status. */
@@ -8928,7 +8944,6 @@
+ ep->hcpriv = NULL;
+done:
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
-+
+}
+
+/** Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
@@ -10085,6 +10100,7 @@
+ DWC_DEBUGPL(DBG_HCD, " Select Transactions\n");
+#endif
+
++ spin_lock(&hcd->lock);
+ /* Process entries in the periodic ready list. */
+ qh_ptr = hcd->periodic_sched_ready.next;
+ while (qh_ptr != &hcd->periodic_sched_ready &&
@@ -10133,6 +10149,7 @@
+
+ hcd->non_periodic_channels++;
+ }
++ spin_unlock(&hcd->lock);
+
+ return ret_val;
+}
@@ -10625,7 +10642,7 @@
+#endif /* DWC_DEVICE_ONLY */
--- /dev/null
+++ b/drivers/usb/dwc/otg_hcd.h
-@@ -0,0 +1,647 @@
+@@ -0,0 +1,652 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.h $
+ * $Revision: #45 $
@@ -10825,6 +10842,9 @@
+ /** (micro)frame at which last start split was initialized. */
+ uint16_t start_split_frame;
+
++ u16 speed;
++ u16 frame_usecs[8];
++
+ /** @} */
+
+ /** Entry for QH in either the periodic or non-periodic schedule. */
@@ -10928,6 +10948,18 @@
+ */
+ uint16_t periodic_usecs;
+
++ /*
++ * Total bandwidth claimed so far for all periodic transfers
++ * in a frame.
++ * This will include a mixture of HS and FS transfers.
++ * Units are microseconds per (micro)frame.
++ * We have a budget per frame and have to schedule
++ * transactions accordingly.
++ * Watch out for the fact that things are actually scheduled for the
++ * "next frame".
++ */
++ u16 frame_usecs[8];
++
+ /**
+ * Frame number read from the core at SOF. The value ranges from 0 to
+ * DWC_HFNUM_MAX_FRNUM.
@@ -11089,6 +11121,7 @@
+/** @{ */
+
+/* Implemented in dwc_otg_hcd_queue.c */
++extern int init_hcd_usecs(dwc_otg_hcd_t *hcd);
+extern dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t *hcd, struct urb *urb);
+extern void dwc_otg_hcd_qh_init(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh, struct urb *urb);
+extern void dwc_otg_hcd_qh_free(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh);
@@ -11130,21 +11163,10 @@
+ kfree(qtd);
+}
+
-+/** Removes a QTD from list.
-+ * @param[in] hcd HCD instance.
-+ * @param[in] qtd QTD to remove from list. */
-+static inline void dwc_otg_hcd_qtd_remove(dwc_otg_hcd_t *hcd, dwc_otg_qtd_t *qtd)
-+{
-+ unsigned long flags;
-+ SPIN_LOCK_IRQSAVE(&hcd->lock, flags);
-+ list_del(&qtd->qtd_list_entry);
-+ SPIN_UNLOCK_IRQRESTORE(&hcd->lock, flags);
-+}
-+
+/** Remove and free a QTD */
+static inline void dwc_otg_hcd_qtd_remove_and_free(dwc_otg_hcd_t *hcd, dwc_otg_qtd_t *qtd)
+{
-+ dwc_otg_hcd_qtd_remove(hcd, qtd);
++ list_del(&qtd->qtd_list_entry);
+ dwc_otg_hcd_qtd_free(qtd);
+}
+
@@ -11275,7 +11297,7 @@
+#endif /* DWC_DEVICE_ONLY */
--- /dev/null
+++ b/drivers/usb/dwc/otg_hcd_intr.c
-@@ -0,0 +1,1826 @@
+@@ -0,0 +1,1828 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_intr.c $
+ * $Revision: #70 $
@@ -11884,6 +11906,7 @@
+
+ DWC_DEBUGPL(DBG_HCDV, " %s(%p,%p,%d)\n", __func__, hcd, qh, free_qtd);
+
++ spin_lock(&hcd->lock);
+ qtd = list_entry(qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry);
+
+ if (qtd->complete_split) {
@@ -11900,6 +11923,7 @@
+
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
++ spin_unlock(&hcd->lock);
+ dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split);
+}
+
@@ -13104,7 +13128,7 @@
+#endif /* DWC_DEVICE_ONLY */
--- /dev/null
+++ b/drivers/usb/dwc/otg_hcd_queue.c
-@@ -0,0 +1,713 @@
+@@ -0,0 +1,794 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_queue.c $
+ * $Revision: #33 $
@@ -13262,6 +13286,7 @@
+ INIT_LIST_HEAD(&qh->qtd_list);
+ INIT_LIST_HEAD(&qh->qh_list_entry);
+ qh->channel = NULL;
++ qh->speed = urb->dev->speed;
+
+ /* FS/LS Enpoint on HS Hub
+ * NOT virtual root hub */
@@ -13283,10 +13308,10 @@
+
+ /** @todo Account for split transfers in the bus time. */
+ int bytecount = dwc_hb_mult(qh->maxp) * dwc_max_packet(qh->maxp);
-+ qh->usecs = usb_calc_bus_time(urb->dev->speed,
++ qh->usecs = NS_TO_US(usb_calc_bus_time(urb->dev->speed,
+ usb_pipein(urb->pipe),
+ (qh->ep_type == USB_ENDPOINT_XFER_ISOC),
-+ bytecount);
++ bytecount));
+
+ /* Start in a slightly future (micro)frame. */
+ qh->sched_frame = dwc_frame_num_inc(hcd->frame_number,
@@ -13365,73 +13390,159 @@
+}
+
+/**
-+ * Checks that a channel is available for a periodic transfer.
-+ *
-+ * @return 0 if successful, negative error code otherise.
++ * Microframe scheduler
++ * track the total use in hcd->frame_usecs
++ * keep each qh use in qh->frame_usecs
++ * when surrendering the qh then donate the time back
+ */
-+static int periodic_channel_available(dwc_otg_hcd_t *hcd)
++static const u16 max_uframe_usecs[] = { 100, 100, 100, 100, 100, 100, 30, 0 };
++
++/*
++ * called from dwc_otg_hcd.c:dwc_otg_hcd_init
++ */
++int init_hcd_usecs(dwc_otg_hcd_t *hcd)
+{
-+ /*
-+ * Currently assuming that there is a dedicated host channnel for each
-+ * periodic transaction plus at least one host channel for
-+ * non-periodic transactions.
-+ */
-+ int status;
-+ int num_channels;
++ int i;
+
-+ num_channels = hcd->core_if->core_params->host_channels;
-+ if ((hcd->periodic_channels + hcd->non_periodic_channels < num_channels) &&
-+ (hcd->periodic_channels < num_channels - 1)) {
-+ status = 0;
-+ }
-+ else {
-+ DWC_NOTICE("%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n",
-+ __func__, num_channels, hcd->periodic_channels,
-+ hcd->non_periodic_channels);
-+ status = -ENOSPC;
-+ }
++ for (i = 0; i < 8; i++)
++ hcd->frame_usecs[i] = max_uframe_usecs[i];
+
-+ return status;
++ return 0;
+}
+
-+/**
-+ * Checks that there is sufficient bandwidth for the specified QH in the
-+ * periodic schedule. For simplicity, this calculation assumes that all the
-+ * transfers in the periodic schedule may occur in the same (micro)frame.
-+ *
-+ * @param hcd The HCD state structure for the DWC OTG controller.
-+ * @param qh QH containing periodic bandwidth required.
-+ *
-+ * @return 0 if successful, negative error code otherwise.
-+ */
-+static int check_periodic_bandwidth(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
++static int find_single_uframe(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
-+ int status;
-+ uint16_t max_claimed_usecs;
++ int i;
++ u16 utime;
++ int t_left;
++ int ret;
++ int done;
++
++ ret = -1;
++ utime = qh->usecs;
++ t_left = utime;
++ i = 0;
++ done = 0;
++ while (done == 0) {
++ /* At the start hcd->frame_usecs[i] = max_uframe_usecs[i]; */
++ if (utime <= hcd->frame_usecs[i]) {
++ hcd->frame_usecs[i] -= utime;
++ qh->frame_usecs[i] += utime;
++ t_left -= utime;
++ ret = i;
++ done = 1;
++ return ret;
++ } else {
++ i++;
++ if (i == 8) {
++ done = 1;
++ ret = -1;
++ }
++ }
++ }
++ return ret;
++}
+
-+ status = 0;
++/*
++ * use this for FS apps that can span multiple uframes
++ */
++static int find_multi_uframe(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
++{
++ int i;
++ int j;
++ u16 utime;
++ int t_left;
++ int ret;
++ int done;
++ u16 xtime;
++
++ ret = -1;
++ utime = qh->usecs;
++ t_left = utime;
++ i = 0;
++ done = 0;
++loop:
++ while (done == 0) {
++ if (hcd->frame_usecs[i] <= 0) {
++ i++;
++ if (i == 8) {
++ done = 1;
++ ret = -1;
++ }
++ goto loop;
++ }
+
-+ if (hcd->core_if->core_params->speed == DWC_SPEED_PARAM_HIGH) {
-+ /*
-+ * High speed mode.
-+ * Max periodic usecs is 80% x 125 usec = 100 usec.
-+ */
-+ max_claimed_usecs = 100 - qh->usecs;
-+ } else {
+ /*
-+ * Full speed mode.
-+ * Max periodic usecs is 90% x 1000 usec = 900 usec.
++ * We need n consequtive slots so use j as a start slot.
++ * j plus j+1 must be enough time (for now)
+ */
-+ max_claimed_usecs = 900 - qh->usecs;
++ xtime = hcd->frame_usecs[i];
++ for (j = i + 1; j < 8; j++) {
++ /*
++ * if we add this frame remaining time to xtime we may
++ * be OK, if not we need to test j for a complete frame.
++ */
++ if ((xtime + hcd->frame_usecs[j]) < utime) {
++ if (hcd->frame_usecs[j] < max_uframe_usecs[j]) {
++ j = 8;
++ ret = -1;
++ continue;
++ }
++ }
++ if (xtime >= utime) {
++ ret = i;
++ j = 8; /* stop loop with a good value ret */
++ continue;
++ }
++ /* add the frame time to x time */
++ xtime += hcd->frame_usecs[j];
++ /* we must have a fully available next frame or break */
++ if ((xtime < utime) &&
++ (hcd->frame_usecs[j] == max_uframe_usecs[j])) {
++ ret = -1;
++ j = 8; /* stop loop with a bad value ret */
++ continue;
++ }
++ }
++ if (ret >= 0) {
++ t_left = utime;
++ for (j = i; (t_left > 0) && (j < 8); j++) {
++ t_left -= hcd->frame_usecs[j];
++ if (t_left <= 0) {
++ qh->frame_usecs[j] +=
++ hcd->frame_usecs[j] + t_left;
++ hcd->frame_usecs[j] = -t_left;
++ ret = i;
++ done = 1;
++ } else {
++ qh->frame_usecs[j] +=
++ hcd->frame_usecs[j];
++ hcd->frame_usecs[j] = 0;
++ }
++ }
++ } else {
++ i++;
++ if (i == 8) {
++ done = 1;
++ ret = -1;
++ }
++ }
+ }
++ return ret;
++}
+
-+ if (hcd->periodic_usecs > max_claimed_usecs) {
-+ DWC_NOTICE("%s: already claimed usecs %d, required usecs %d\n",
-+ __func__, hcd->periodic_usecs, qh->usecs);
-+ status = -ENOSPC;
-+ }
++static int find_uframe(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
++{
++ int ret = -1;
+
-+ return status;
++ if (qh->speed == USB_SPEED_HIGH)
++ /* if this is a hs transaction we need a full frame */
++ ret = find_single_uframe(hcd, qh);
++ else
++ /* FS transaction may need a sequence of frames */
++ ret = find_multi_uframe(hcd, qh);
++
++ return ret;
+}
+
+/**
@@ -13467,58 +13578,55 @@
+
+/**
+ * Schedules an interrupt or isochronous transfer in the periodic schedule.
-+ *
-+ * @param hcd The HCD state structure for the DWC OTG controller.
-+ * @param qh QH for the periodic transfer. The QH should already contain the
-+ * scheduling information.
-+ *
-+ * @return 0 if successful, negative error code otherwise.
+ */
+static int schedule_periodic(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
-+ int status = 0;
++ int status;
++ struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
++ int frame;
+
-+ status = periodic_channel_available(hcd);
-+ if (status) {
-+ DWC_NOTICE("%s: No host channel available for periodic "
-+ "transfer.\n", __func__);
-+ return status;
++ status = find_uframe(hcd, qh);
++ frame = -1;
++ if (status == 0) {
++ frame = 7;
++ } else {
++ if (status > 0)
++ frame = status - 1;
+ }
-+
-+ status = check_periodic_bandwidth(hcd, qh);
++ /* Set the new frame up */
++ if (frame > -1) {
++ qh->sched_frame &= ~0x7;
++ qh->sched_frame |= (frame & 7);
++ }
++ if (status != -1)
++ status = 0;
+ if (status) {
-+ DWC_NOTICE("%s: Insufficient periodic bandwidth for "
-+ "periodic transfer.\n", __func__);
++ pr_notice("%s: Insufficient periodic bandwidth for "
++ "periodic transfer.\n", __func__);
+ return status;
+ }
-+
+ status = check_max_xfer_size(hcd, qh);
+ if (status) {
-+ DWC_NOTICE("%s: Channel max transfer size too small "
-+ "for periodic transfer.\n", __func__);
++ pr_notice("%s: Channel max transfer size too small "
++ "for periodic transfer.\n", __func__);
+ return status;
+ }
-+
+ /* Always start in the inactive schedule. */
+ list_add_tail(&qh->qh_list_entry, &hcd->periodic_sched_inactive);
+
-+ /* Reserve the periodic channel. */
-+ hcd->periodic_channels++;
-+
+ /* Update claimed usecs per (micro)frame. */
+ hcd->periodic_usecs += qh->usecs;
+
-+ /* Update average periodic bandwidth claimed and # periodic reqs for usbfs. */
-+ hcd_to_bus(dwc_otg_hcd_to_hcd(hcd))->bandwidth_allocated += qh->usecs / qh->interval;
-+ if (qh->ep_type == USB_ENDPOINT_XFER_INT) {
-+ hcd_to_bus(dwc_otg_hcd_to_hcd(hcd))->bandwidth_int_reqs++;
-+ DWC_DEBUGPL(DBG_HCD, "Scheduled intr: qh %p, usecs %d, period %d\n",
-+ qh, qh->usecs, qh->interval);
-+ } else {
-+ hcd_to_bus(dwc_otg_hcd_to_hcd(hcd))->bandwidth_isoc_reqs++;
-+ DWC_DEBUGPL(DBG_HCD, "Scheduled isoc: qh %p, usecs %d, period %d\n",
-+ qh, qh->usecs, qh->interval);
-+ }
++ /*
++ * Update average periodic bandwidth claimed and # periodic reqs for
++ * usbfs.
++ */
++ bus->bandwidth_allocated += qh->usecs / qh->interval;
++
++ if (qh->ep_type == USB_ENDPOINT_XFER_INT)
++ bus->bandwidth_int_reqs++;
++ else
++ bus->bandwidth_isoc_reqs++;
+
+ return status;
+}
@@ -13569,32 +13677,29 @@
+
+/**
+ * Removes an interrupt or isochronous transfer from the periodic schedule.
-+ *
-+ * @param hcd The HCD state structure for the DWC OTG controller.
-+ * @param qh QH for the periodic transfer.
+ */
+static void deschedule_periodic(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
-+ list_del_init(&qh->qh_list_entry);
-+
-+ /* Release the periodic channel reservation. */
-+ hcd->periodic_channels--;
++ struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
++ int i;
+
++ list_del_init(&qh->qh_list_entry);
+ /* Update claimed usecs per (micro)frame. */
+ hcd->periodic_usecs -= qh->usecs;
-+
-+ /* Update average periodic bandwidth claimed and # periodic reqs for usbfs. */
-+ hcd_to_bus(dwc_otg_hcd_to_hcd(hcd))->bandwidth_allocated -= qh->usecs / qh->interval;
-+
-+ if (qh->ep_type == USB_ENDPOINT_XFER_INT) {
-+ hcd_to_bus(dwc_otg_hcd_to_hcd(hcd))->bandwidth_int_reqs--;
-+ DWC_DEBUGPL(DBG_HCD, "Descheduled intr: qh %p, usecs %d, period %d\n",
-+ qh, qh->usecs, qh->interval);
-+ } else {
-+ hcd_to_bus(dwc_otg_hcd_to_hcd(hcd))->bandwidth_isoc_reqs--;
-+ DWC_DEBUGPL(DBG_HCD, "Descheduled isoc: qh %p, usecs %d, period %d\n",
-+ qh, qh->usecs, qh->interval);
++ for (i = 0; i < 8; i++) {
++ hcd->frame_usecs[i] += qh->frame_usecs[i];
++ qh->frame_usecs[i] = 0;
+ }
++ /*
++ * Update average periodic bandwidth claimed and # periodic reqs for
++ * usbfs.
++ */
++ bus->bandwidth_allocated -= qh->usecs / qh->interval;
++
++ if (qh->ep_type == USB_ENDPOINT_XFER_INT)
++ bus->bandwidth_int_reqs--;
++ else
++ bus->bandwidth_isoc_reqs--;
+}
+
+/**