Reimagine the USB MSC worker thread as a kernel thread (instead of a pthread)
This commit is contained in:
parent
93380d8156
commit
b951732a2b
14
ChangeLog
14
ChangeLog
@ -7031,3 +7031,17 @@
|
|||||||
Kconfig; update naming to include SAM34_ (2014-3-24).
|
Kconfig; update naming to include SAM34_ (2014-3-24).
|
||||||
* configs/sam4e-ek/include/board.h: Update HSMCI timing to use the
|
* configs/sam4e-ek/include/board.h: Update HSMCI timing to use the
|
||||||
CLKODD bit (2014-3-24).
|
CLKODD bit (2014-3-24).
|
||||||
|
* drivers/include/mtd/Kconfig, sector512.c, and include/nuttx/mtd/mtd.h:
|
||||||
|
Add a new MTD driver that can be used to contain another driver and
|
||||||
|
force its apparent sector size to be 512 bytes (2014-3-24).
|
||||||
|
* arch/arm/src/sam34/sam_lowputc.c sam_serial.c: Fix a mysterious
|
||||||
|
multithreading bug that can log up the serial port (2014-3-14).
|
||||||
|
* drivers/usbdev/Kconfig, usbmsc.c, usbmsc.h, and usbmsc_scsi.c:
|
||||||
|
Redesign threading module used with the USB MSC driver. It was using
|
||||||
|
pthreads before and these were changed to a kernel thread. The reason
|
||||||
|
for this has to do with task grouping: A pthread is a memory of the
|
||||||
|
group of the task that started it. A kernel thread is independent of
|
||||||
|
the task that started in (other than knowing it as the parent). This
|
||||||
|
allows me to remove so kludge logic to "deparent" the pthread on
|
||||||
|
startup (2014-3-25).
|
||||||
|
|
||||||
|
@ -584,10 +584,25 @@ config USBMSC_VERSIONNO
|
|||||||
default "0x399"
|
default "0x399"
|
||||||
|
|
||||||
config USBMSC_REMOVABLE
|
config USBMSC_REMOVABLE
|
||||||
bool "Mass storage remove able"
|
bool "Mass storage removable"
|
||||||
default n
|
default n
|
||||||
---help---
|
---help---
|
||||||
Select if the media is removable
|
Select if the media is removable
|
||||||
USB Composite Device Configuration
|
USB Composite Device Configuration
|
||||||
|
|
||||||
|
config USBMSC_SCSI_PRIO
|
||||||
|
int "USBMSC SCSI daemon priority"
|
||||||
|
default 128
|
||||||
|
---help---
|
||||||
|
Priority of the SCSI kernel thread. This must be a relatively high
|
||||||
|
priority so that the SCSI daemon can be response to USB block driver
|
||||||
|
accesses.
|
||||||
|
|
||||||
|
config USBMSC_SCSI_STACKSIZE
|
||||||
|
int "USBMSC SCSI daemon stack size"
|
||||||
|
default 2048
|
||||||
|
---help---
|
||||||
|
Stack size used with the SCSI kernel thread. The default value
|
||||||
|
is not tuned.
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
@ -66,13 +66,13 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
#include <debug.h>
|
#include <debug.h>
|
||||||
|
|
||||||
#include <nuttx/kmalloc.h>
|
#include <nuttx/kmalloc.h>
|
||||||
|
#include <nuttx/kthread.h>
|
||||||
#include <nuttx/arch.h>
|
#include <nuttx/arch.h>
|
||||||
#include <nuttx/fs/fs.h>
|
#include <nuttx/fs/fs.h>
|
||||||
#include <nuttx/usb/usb.h>
|
#include <nuttx/usb/usb.h>
|
||||||
@ -160,6 +160,10 @@ static struct usbdevclass_driverops_s g_driverops =
|
|||||||
NULL /* resume */
|
NULL /* resume */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Used to hand-off the state structure when the SCSI worker thread is started */
|
||||||
|
|
||||||
|
FAR struct usbmsc_dev_s *g_usbmsc_handoff;
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Public Data
|
* Public Data
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@ -642,7 +646,7 @@ static int usbmsc_setup(FAR struct usbdevclass_driver_s *driver,
|
|||||||
|
|
||||||
priv->theventset |= USBMSC_EVENT_CFGCHANGE;
|
priv->theventset |= USBMSC_EVENT_CFGCHANGE;
|
||||||
priv->thvalue = value;
|
priv->thvalue = value;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
|
|
||||||
/* Return here... the response will be provided later by the
|
/* Return here... the response will be provided later by the
|
||||||
* worker thread.
|
* worker thread.
|
||||||
@ -681,7 +685,7 @@ static int usbmsc_setup(FAR struct usbdevclass_driver_s *driver,
|
|||||||
/* Signal to instantiate the interface change */
|
/* Signal to instantiate the interface change */
|
||||||
|
|
||||||
priv->theventset |= USBMSC_EVENT_IFCHANGE;
|
priv->theventset |= USBMSC_EVENT_IFCHANGE;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
|
|
||||||
/* Return here... the response will be provided later by the
|
/* Return here... the response will be provided later by the
|
||||||
* worker thread.
|
* worker thread.
|
||||||
@ -748,7 +752,7 @@ static int usbmsc_setup(FAR struct usbdevclass_driver_s *driver,
|
|||||||
/* Signal to stop the current operation and reinitialize state */
|
/* Signal to stop the current operation and reinitialize state */
|
||||||
|
|
||||||
priv->theventset |= USBMSC_EVENT_RESET;
|
priv->theventset |= USBMSC_EVENT_RESET;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
|
|
||||||
/* Return here... the response will be provided later by the
|
/* Return here... the response will be provided later by the
|
||||||
* worker thread.
|
* worker thread.
|
||||||
@ -870,7 +874,7 @@ static void usbmsc_disconnect(FAR struct usbdevclass_driver_s *driver,
|
|||||||
/* Signal the worker thread */
|
/* Signal the worker thread */
|
||||||
|
|
||||||
priv->theventset |= USBMSC_EVENT_DISCONNECT;
|
priv->theventset |= USBMSC_EVENT_DISCONNECT;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
irqrestore(flags);
|
irqrestore(flags);
|
||||||
|
|
||||||
/* Perform the soft connect function so that we will we can be
|
/* Perform the soft connect function so that we will we can be
|
||||||
@ -1109,7 +1113,7 @@ void usbmsc_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
|
|||||||
/* Inform the worker thread that a write request has been returned */
|
/* Inform the worker thread that a write request has been returned */
|
||||||
|
|
||||||
priv->theventset |= USBMSC_EVENT_WRCOMPLETE;
|
priv->theventset |= USBMSC_EVENT_WRCOMPLETE;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
@ -1160,7 +1164,7 @@ void usbmsc_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
|
|||||||
/* Signal the worker thread that there is received data to be processed */
|
/* Signal the worker thread that there is received data to be processed */
|
||||||
|
|
||||||
priv->theventset |= USBMSC_EVENT_RDCOMPLETE;
|
priv->theventset |= USBMSC_EVENT_RDCOMPLETE;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1261,6 +1265,26 @@ void usbmsc_deferredresponse(FAR struct usbmsc_dev_s *priv, bool failed)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: usbmsc_sync_wait
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Wait for the worker thread to obtain the USB MSC state data
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static inline void usbmsc_sync_wait(FAR struct usbmsc_dev_s *priv)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = sem_wait(&priv->thsynch);
|
||||||
|
DEBUGASSERT(ret == OK || errno == EINTR);
|
||||||
|
}
|
||||||
|
while (ret < 0);
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* User Interfaces
|
* User Interfaces
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@ -1314,8 +1338,9 @@ int usbmsc_configure(unsigned int nluns, void **handle)
|
|||||||
priv = &alloc->dev;
|
priv = &alloc->dev;
|
||||||
memset(priv, 0, sizeof(struct usbmsc_dev_s));
|
memset(priv, 0, sizeof(struct usbmsc_dev_s));
|
||||||
|
|
||||||
pthread_mutex_init(&priv->mutex, NULL);
|
sem_init(&priv->thsynch, 0, 0);
|
||||||
pthread_cond_init(&priv->cond, NULL);
|
sem_init(&priv->thlock, 0, 1);
|
||||||
|
sem_init(&priv->thwaitsem, 0, 0);
|
||||||
sq_init(&priv->wrreqlist);
|
sq_init(&priv->wrreqlist);
|
||||||
|
|
||||||
priv->nluns = nluns;
|
priv->nluns = nluns;
|
||||||
@ -1549,7 +1574,7 @@ int usbmsc_unbindlun(FAR void *handle, unsigned int lunno)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
lun = &priv->luntab[lunno];
|
lun = &priv->luntab[lunno];
|
||||||
pthread_mutex_lock(&priv->mutex);
|
usbmsc_scsi_lock(priv);
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUG
|
#ifdef CONFIG_DEBUG
|
||||||
if (lun->inode == NULL)
|
if (lun->inode == NULL)
|
||||||
@ -1566,7 +1591,7 @@ int usbmsc_unbindlun(FAR void *handle, unsigned int lunno)
|
|||||||
ret = OK;
|
ret = OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
usbmsc_scsi_unlock(priv);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1594,10 +1619,7 @@ int usbmsc_exportluns(FAR void *handle)
|
|||||||
FAR struct usbmsc_dev_s *priv;
|
FAR struct usbmsc_dev_s *priv;
|
||||||
FAR struct usbmsc_driver_s *drvr;
|
FAR struct usbmsc_driver_s *drvr;
|
||||||
irqstate_t flags;
|
irqstate_t flags;
|
||||||
#ifdef SDCC
|
int ret = OK;
|
||||||
pthread_attr_t attr;
|
|
||||||
#endif
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUG
|
#ifdef CONFIG_DEBUG
|
||||||
if (!alloc)
|
if (!alloc)
|
||||||
@ -1610,35 +1632,35 @@ int usbmsc_exportluns(FAR void *handle)
|
|||||||
priv = &alloc->dev;
|
priv = &alloc->dev;
|
||||||
drvr = &alloc->drvr;
|
drvr = &alloc->drvr;
|
||||||
|
|
||||||
/* Start the worker thread */
|
/* Start the worker thread
|
||||||
|
|
||||||
pthread_mutex_lock(&priv->mutex);
|
|
||||||
priv->thstate = USBMSC_STATE_NOTSTARTED;
|
|
||||||
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
|
||||||
|
|
||||||
#ifdef SDCC
|
|
||||||
(void)pthread_attr_init(&attr);
|
|
||||||
ret = pthread_create(&priv->thread, &attr, usbmsc_workerthread, (pthread_addr_t)priv);
|
|
||||||
#else
|
|
||||||
ret = pthread_create(&priv->thread, NULL, usbmsc_workerthread, (pthread_addr_t)priv);
|
|
||||||
#endif
|
|
||||||
if (ret != OK)
|
|
||||||
{
|
|
||||||
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_THREADCREATE), (uint16_t)-ret);
|
|
||||||
goto errout_with_mutex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Detach the pthread so that we do not create a memory leak.
|
|
||||||
*
|
*
|
||||||
* REVISIT: See related comments in usbmsc_uninitialize()
|
* REVISIT: g_usbmsc_handoff is a global and, hence, really requires
|
||||||
|
* some protection against re-entrant usage.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ret = pthread_detach(priv->thread);
|
usbmsc_scsi_lock(priv);
|
||||||
if (ret != OK)
|
|
||||||
|
priv->thstate = USBMSC_STATE_NOTSTARTED;
|
||||||
|
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
||||||
|
|
||||||
|
g_usbmsc_handoff = priv;
|
||||||
|
|
||||||
|
uvdbg("Starting SCSI worker thread\n");
|
||||||
|
priv->thpid = KERNEL_THREAD("scsid", CONFIG_USBMSC_SCSI_PRIO,
|
||||||
|
CONFIG_USBMSC_SCSI_STACKSIZE,
|
||||||
|
usbmsc_scsi_main, NULL);
|
||||||
|
if (priv->thpid <= 0)
|
||||||
{
|
{
|
||||||
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_DETACH), (uint16_t)-ret);
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_THREADCREATE), (uint16_t)errno);
|
||||||
|
goto errout_with_lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wait for the worker thread to run and initialize */
|
||||||
|
|
||||||
|
uvdbg("Waiting for the SCSI worker thread\n");
|
||||||
|
usbmsc_sync_wait(priv);
|
||||||
|
DEBUGASSERT(g_usbmsc_handoff == NULL);
|
||||||
|
|
||||||
/* Register the USB storage class driver (unless we are part of a composite device) */
|
/* Register the USB storage class driver (unless we are part of a composite device) */
|
||||||
|
|
||||||
#ifndef CONFIG_USBMSC_COMPOSITE
|
#ifndef CONFIG_USBMSC_COMPOSITE
|
||||||
@ -1646,19 +1668,20 @@ int usbmsc_exportluns(FAR void *handle)
|
|||||||
if (ret != OK)
|
if (ret != OK)
|
||||||
{
|
{
|
||||||
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_DEVREGISTER), (uint16_t)-ret);
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_DEVREGISTER), (uint16_t)-ret);
|
||||||
goto errout_with_mutex;
|
goto errout_with_lock;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Signal to start the thread */
|
/* Signal to start the thread */
|
||||||
|
|
||||||
|
uvdbg("Signalling for the SCSI worker thread\n");
|
||||||
flags = irqsave();
|
flags = irqsave();
|
||||||
priv->theventset |= USBMSC_EVENT_READY;
|
priv->theventset |= USBMSC_EVENT_READY;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
irqrestore(flags);
|
irqrestore(flags);
|
||||||
|
|
||||||
errout_with_mutex:
|
errout_with_lock:
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
usbmsc_scsi_unlock(priv);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1742,7 +1765,7 @@ void usbmsc_uninitialize(FAR void *handle)
|
|||||||
* first pass uninitialization.
|
* first pass uninitialization.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (priv->thread == 0)
|
if (priv->thpid == 0)
|
||||||
{
|
{
|
||||||
/* In this second and final pass, all that remains to be done is to
|
/* In this second and final pass, all that remains to be done is to
|
||||||
* free the memory resources.
|
* free the memory resources.
|
||||||
@ -1759,54 +1782,28 @@ void usbmsc_uninitialize(FAR void *handle)
|
|||||||
{
|
{
|
||||||
/* The thread was started.. Is it still running? */
|
/* The thread was started.. Is it still running? */
|
||||||
|
|
||||||
pthread_mutex_lock(&priv->mutex);
|
usbmsc_scsi_lock(priv);
|
||||||
if (priv->thstate != USBMSC_STATE_TERMINATED)
|
if (priv->thstate != USBMSC_STATE_TERMINATED)
|
||||||
{
|
{
|
||||||
/* Yes.. Ask the thread to stop */
|
/* Yes.. Ask the thread to stop */
|
||||||
|
|
||||||
flags = irqsave();
|
flags = irqsave();
|
||||||
priv->theventset |= USBMSC_EVENT_TERMINATEREQUEST;
|
priv->theventset |= USBMSC_EVENT_TERMINATEREQUEST;
|
||||||
pthread_cond_signal(&priv->cond);
|
usbmsc_scsi_signal(priv);
|
||||||
irqrestore(flags);
|
irqrestore(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
usbmsc_scsi_unlock(priv);
|
||||||
|
|
||||||
/* Wait for the thread to exit. This is necessary even if the
|
/* Wait for the thread to exit */
|
||||||
* thread has already exitted in order to collect the join
|
|
||||||
* garbage
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
/* REVISIT: pthread_join does not work in all contexts. In
|
|
||||||
* particular, if usbmsc_uninitialize() executes in a different
|
|
||||||
* task group than the group that includes the SCSI thread, then
|
|
||||||
* pthread_join will fail.
|
|
||||||
*
|
|
||||||
* NOTE: If, for some reason, you wanted to restore this code,
|
|
||||||
* there is now a matching pthread_detach() elsewhere to prevent
|
|
||||||
* memory leaks.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(void)pthread_join(priv->thread, &value);
|
|
||||||
|
|
||||||
#else
|
|
||||||
/* REVISIT: Calling pthread_mutex_lock and pthread_cond_wait
|
|
||||||
* from outside of the task group is equally non-standard.
|
|
||||||
* However, this actually works.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pthread_mutex_lock(&priv->mutex);
|
|
||||||
while ((priv->theventset & USBMSC_EVENT_TERMINATEREQUEST) != 0)
|
while ((priv->theventset & USBMSC_EVENT_TERMINATEREQUEST) != 0)
|
||||||
{
|
{
|
||||||
pthread_cond_wait(&priv->cond, &priv->mutex);
|
usbmsc_sync_wait(priv);
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
priv->thread = 0;
|
priv->thpid = 0;
|
||||||
|
|
||||||
/* Unregister the driver (unless we are a part of a composite device) */
|
/* Unregister the driver (unless we are a part of a composite device) */
|
||||||
|
|
||||||
@ -1832,14 +1829,15 @@ void usbmsc_uninitialize(FAR void *handle)
|
|||||||
|
|
||||||
/* Uninitialize and release the driver structure */
|
/* Uninitialize and release the driver structure */
|
||||||
|
|
||||||
pthread_mutex_destroy(&priv->mutex);
|
sem_destroy(&priv->thsynch);
|
||||||
pthread_cond_destroy(&priv->cond);
|
sem_destroy(&priv->thlock);
|
||||||
|
sem_destroy(&priv->thwaitsem);
|
||||||
|
|
||||||
#ifndef CONFIG_USBMSC_COMPOSITE
|
#ifndef CONFIG_USBMSC_COMPOSITE
|
||||||
/* For the case of the composite driver, there is a two pass
|
/* For the case of the composite driver, there is a two pass
|
||||||
* uninitialization sequence. We cannot yet free the driver structure.
|
* uninitialization sequence. We cannot yet free the driver structure.
|
||||||
* We will do that on the second pass (and we will know that it is the
|
* We will do that on the second pass (and we will know that it is the
|
||||||
* second pass because of priv->thread == 0)
|
* second pass because of priv->thpid == 0)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
kfree(priv);
|
kfree(priv);
|
||||||
|
@ -47,8 +47,8 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
|
||||||
#include <nuttx/fs/fs.h>
|
#include <nuttx/fs/fs.h>
|
||||||
#include <nuttx/usb/storage.h>
|
#include <nuttx/usb/storage.h>
|
||||||
@ -194,6 +194,16 @@
|
|||||||
#undef CONFIG_USBMSC_CONFIGSTR
|
#undef CONFIG_USBMSC_CONFIGSTR
|
||||||
#define CONFIG_USBMSC_CONFIGSTR "Bulk"
|
#define CONFIG_USBMSC_CONFIGSTR "Bulk"
|
||||||
|
|
||||||
|
/* SCSI daemon */
|
||||||
|
|
||||||
|
#ifndef CONFIG_USBMSC_SCSI_PRIO
|
||||||
|
# define CONFIG_USBMSC_SCSI_PRIO 128
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CONFIG_USBMSC_SCSI_STACKSIZE
|
||||||
|
# define CONFIG_USBMSC_SCSI_STACKSIZE 2048
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Debug -- must be consistent with include/debug.h */
|
/* Debug -- must be consistent with include/debug.h */
|
||||||
|
|
||||||
#ifdef CONFIG_CPP_HAVE_VARARGS
|
#ifdef CONFIG_CPP_HAVE_VARARGS
|
||||||
@ -443,17 +453,19 @@ struct usbmsc_lun_s
|
|||||||
size_t nsectors; /* Number of sectors in the partition */
|
size_t nsectors; /* Number of sectors in the partition */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Describes the overall state of the driver */
|
/* Describes the overall state of one instance of the driver */
|
||||||
|
|
||||||
struct usbmsc_dev_s
|
struct usbmsc_dev_s
|
||||||
{
|
{
|
||||||
FAR struct usbdev_s *usbdev; /* usbdev driver pointer (Non-null if registered) */
|
FAR struct usbdev_s *usbdev; /* usbdev driver pointer (Non-null if registered) */
|
||||||
|
|
||||||
/* Worker thread interface */
|
/* SCSI worker kernel thread interface */
|
||||||
|
|
||||||
pthread_t thread; /* The worker thread */
|
pid_t thpid; /* The worker thread task ID */
|
||||||
pthread_mutex_t mutex; /* Mutually exclusive access to resources*/
|
sem_t thsynch; /* Used to synchronizer terminal events */
|
||||||
pthread_cond_t cond; /* Used to signal worker thread */
|
sem_t thlock; /* Used to get exclusive access to the state data */
|
||||||
|
sem_t thwaitsem; /* Used to signal worker thread */
|
||||||
|
volatile bool thwaiting; /* True: worker thread is waiting for an event */
|
||||||
volatile uint8_t thstate; /* State of the worker thread */
|
volatile uint8_t thstate; /* State of the worker thread */
|
||||||
volatile uint16_t theventset; /* Set of pending events signaled to worker thread */
|
volatile uint16_t theventset; /* Set of pending events signaled to worker thread */
|
||||||
volatile uint8_t thvalue; /* Value passed with the event (must persist) */
|
volatile uint8_t thvalue; /* Value passed with the event (must persist) */
|
||||||
@ -542,10 +554,55 @@ EXTERN const char g_compserialstr[];
|
|||||||
#define g_mscproductstr g_compproductstr
|
#define g_mscproductstr g_compproductstr
|
||||||
#define g_mscserialstr g_compserialstr
|
#define g_mscserialstr g_compserialstr
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Used to hand-off the state structure when the SCSI worker thread is started */
|
||||||
|
|
||||||
|
EXTERN FAR struct usbmsc_dev_s *g_usbmsc_handoff;
|
||||||
|
|
||||||
/************************************************************************************
|
/************************************************************************************
|
||||||
* Public Function Prototypes
|
* Public Function Prototypes
|
||||||
************************************************************************************/
|
************************************************************************************/
|
||||||
|
|
||||||
|
/************************************************************************************
|
||||||
|
* Name: usbmsc_scsi_lock
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Get exclusive access to SCSI state data.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
void usbmsc_scsi_lock(FAR struct usbmsc_dev_s *priv);
|
||||||
|
|
||||||
|
/************************************************************************************
|
||||||
|
* Name: usbmsc_scsi_unlock
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Relinquish exclusive access to SCSI state data.
|
||||||
|
*
|
||||||
|
************************************************************************************/
|
||||||
|
|
||||||
|
#define usbmsc_scsi_unlock(priv) sem_post(&priv->thlock)
|
||||||
|
|
||||||
|
/************************************************************************************
|
||||||
|
* Name: usbmsc_scsi_signal
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Signal the SCSI worker thread that SCSI events need service.
|
||||||
|
*
|
||||||
|
************************************************************************************/
|
||||||
|
|
||||||
|
void usbmsc_scsi_signal(FAR struct usbmsc_dev_s *priv);
|
||||||
|
|
||||||
|
/************************************************************************************
|
||||||
|
* Name: usbmsc_synch_signal
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* ACK controlling tasks request for synchronization.
|
||||||
|
*
|
||||||
|
************************************************************************************/
|
||||||
|
|
||||||
|
#define usbmsc_synch_signal(priv) sem_post(&priv->thsynch)
|
||||||
|
|
||||||
/************************************************************************************
|
/************************************************************************************
|
||||||
* Name: usbmsc_mkstrdesc
|
* Name: usbmsc_mkstrdesc
|
||||||
*
|
*
|
||||||
@ -607,7 +664,7 @@ FAR const struct usb_qualdesc_s *usbmsc_getqualdesc(void);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_workerthread
|
* Name: usbmsc_scsi_main
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* This is the main function of the USB storage worker thread. It loops
|
* This is the main function of the USB storage worker thread. It loops
|
||||||
@ -615,7 +672,7 @@ FAR const struct usb_qualdesc_s *usbmsc_getqualdesc(void);
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
EXTERN void *usbmsc_workerthread(void *arg);
|
int usbmsc_scsi_main(int argc, char *argv[]);
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_setconfig
|
* Name: usbmsc_setconfig
|
||||||
@ -626,7 +683,7 @@ EXTERN void *usbmsc_workerthread(void *arg);
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
EXTERN int usbmsc_setconfig(FAR struct usbmsc_dev_s *priv, uint8_t config);
|
int usbmsc_setconfig(FAR struct usbmsc_dev_s *priv, uint8_t config);
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_resetconfig
|
* Name: usbmsc_resetconfig
|
||||||
@ -636,7 +693,7 @@ EXTERN int usbmsc_setconfig(FAR struct usbmsc_dev_s *priv, uint8_t config);
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
EXTERN void usbmsc_resetconfig(FAR struct usbmsc_dev_s *priv);
|
void usbmsc_resetconfig(FAR struct usbmsc_dev_s *priv);
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_wrcomplete
|
* Name: usbmsc_wrcomplete
|
||||||
@ -647,8 +704,8 @@ EXTERN void usbmsc_resetconfig(FAR struct usbmsc_dev_s *priv);
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
EXTERN void usbmsc_wrcomplete(FAR struct usbdev_ep_s *ep,
|
void usbmsc_wrcomplete(FAR struct usbdev_ep_s *ep,
|
||||||
FAR struct usbdev_req_s *req);
|
FAR struct usbdev_req_s *req);
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_rdcomplete
|
* Name: usbmsc_rdcomplete
|
||||||
@ -659,8 +716,8 @@ EXTERN void usbmsc_wrcomplete(FAR struct usbdev_ep_s *ep,
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
EXTERN void usbmsc_rdcomplete(FAR struct usbdev_ep_s *ep,
|
void usbmsc_rdcomplete(FAR struct usbdev_ep_s *ep,
|
||||||
FAR struct usbdev_req_s *req);
|
FAR struct usbdev_req_s *req);
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_deferredresponse
|
* Name: usbmsc_deferredresponse
|
||||||
@ -684,7 +741,7 @@ EXTERN void usbmsc_rdcomplete(FAR struct usbdev_ep_s *ep,
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
EXTERN void usbmsc_deferredresponse(FAR struct usbmsc_dev_s *priv, bool failed);
|
void usbmsc_deferredresponse(FAR struct usbmsc_dev_s *priv, bool failed);
|
||||||
|
|
||||||
#undef EXTERN
|
#undef EXTERN
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
|
@ -61,12 +61,13 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <pthread.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
#include <debug.h>
|
#include <debug.h>
|
||||||
|
|
||||||
|
#include <nuttx/kthread.h>
|
||||||
#include <nuttx/arch.h>
|
#include <nuttx/arch.h>
|
||||||
#include <nuttx/scsi.h>
|
#include <nuttx/scsi.h>
|
||||||
#include <nuttx/usb/storage.h>
|
#include <nuttx/usb/storage.h>
|
||||||
@ -353,6 +354,52 @@ static void usbmsc_putle32(uint8_t *buf, uint32_t val)
|
|||||||
* SCSI Worker Thread
|
* SCSI Worker Thread
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: usbmsc_scsi_wait
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Wait for a SCSI worker thread event.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static void usbmsc_scsi_wait(FAR struct usbmsc_dev_s *priv)
|
||||||
|
{
|
||||||
|
irqstate_t flags = irqsave();
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* We must hold the SCSI lock to call this function */
|
||||||
|
|
||||||
|
DEBUGASSERT(priv->thlock.semcount < 1);
|
||||||
|
|
||||||
|
/* A flag is used to prevent driving up the semaphore count. This function
|
||||||
|
* is called (primarily) from the SCSI work thread so we must disable
|
||||||
|
* interrupts momentarily to assure that test of the flag and the wait fo
|
||||||
|
* the semaphore count are atomic. Interrupts will, of course, be re-
|
||||||
|
* enabled while we wait for the event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
flags = irqsave();
|
||||||
|
priv->thwaiting = true;
|
||||||
|
|
||||||
|
/* Relinquish our lock on the SCSI state data */
|
||||||
|
|
||||||
|
usbmsc_scsi_unlock(priv);
|
||||||
|
|
||||||
|
/* Now wait for a SCSI event to be signalled */
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = sem_wait(&priv->thwaitsem);
|
||||||
|
DEBUGASSERT(ret == OK || errno == EINTR);
|
||||||
|
}
|
||||||
|
while (priv->thwaiting);
|
||||||
|
|
||||||
|
/* Re-acquire our lock on the SCSI state data */
|
||||||
|
|
||||||
|
usbmsc_scsi_lock(priv);
|
||||||
|
irqrestore(flags);
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_cmdtestunitready
|
* Name: usbmsc_cmdtestunitready
|
||||||
*
|
*
|
||||||
@ -406,8 +453,8 @@ static inline int usbmsc_cmdrequestsense(FAR struct usbmsc_dev_s *priv,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = usbmsc_setupcmd(priv, cdblen,
|
ret = usbmsc_setupcmd(priv, cdblen,
|
||||||
USBMSC_FLAGS_DIRDEVICE2HOST|USBMSC_FLAGS_LUNNOTNEEDED|
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_LUNNOTNEEDED |
|
||||||
USBMSC_FLAGS_UACOKAY|USBMSC_FLAGS_RETAINSENSEDATA);
|
USBMSC_FLAGS_UACOKAY | USBMSC_FLAGS_RETAINSENSEDATA);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
lun = priv->lun;
|
lun = priv->lun;
|
||||||
@ -467,12 +514,14 @@ static inline int usbmsc_cmdread6(FAR struct usbmsc_dev_s *priv)
|
|||||||
priv->u.xfrlen = 256;
|
priv->u.xfrlen = 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_READ6_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST|USBMSC_FLAGS_BLOCKXFR);
|
ret = usbmsc_setupcmd(priv, SCSICMD_READ6_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_BLOCKXFR);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
||||||
|
|
||||||
priv->sector = (uint32_t)(read6->mslba & SCSICMD_READ6_MSLBAMASK) << 16 | (uint32_t)usbmsc_getbe16(read6->lslba);
|
priv->sector = (uint32_t)(read6->mslba & SCSICMD_READ6_MSLBAMASK) << 16 |
|
||||||
|
(uint32_t)usbmsc_getbe16(read6->lslba);
|
||||||
|
|
||||||
/* Verify that a block driver has been bound to the LUN */
|
/* Verify that a block driver has been bound to the LUN */
|
||||||
|
|
||||||
@ -496,10 +545,12 @@ static inline int usbmsc_cmdread6(FAR struct usbmsc_dev_s *priv)
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDREAD6), priv->cdb[0]);
|
usbtrace(TRACE_CLASSSTATE(USBMSC_CLASSSTATE_CMDPARSECMDREAD6),
|
||||||
|
priv->cdb[0]);
|
||||||
priv->thstate = USBMSC_STATE_CMDREAD;
|
priv->thstate = USBMSC_STATE_CMDREAD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,7 +574,8 @@ static inline int usbmsc_cmdwrite6(FAR struct usbmsc_dev_s *priv)
|
|||||||
priv->u.xfrlen = 256;
|
priv->u.xfrlen = 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE6_SIZEOF, USBMSC_FLAGS_DIRHOST2DEVICE|USBMSC_FLAGS_BLOCKXFR);
|
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE6_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRHOST2DEVICE | USBMSC_FLAGS_BLOCKXFR);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
||||||
@ -565,6 +617,7 @@ static inline int usbmsc_cmdwrite6(FAR struct usbmsc_dev_s *priv)
|
|||||||
priv->thstate = USBMSC_STATE_CMDWRITE;
|
priv->thstate = USBMSC_STATE_CMDWRITE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -586,7 +639,8 @@ static inline int usbmsc_cmdinquiry(FAR struct usbmsc_dev_s *priv,
|
|||||||
|
|
||||||
priv->u.alloclen = usbmsc_getbe16(inquiry->alloclen);
|
priv->u.alloclen = usbmsc_getbe16(inquiry->alloclen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_INQUIRY_SIZEOF,
|
ret = usbmsc_setupcmd(priv, SCSICMD_INQUIRY_SIZEOF,
|
||||||
USBMSC_FLAGS_DIRDEVICE2HOST|USBMSC_FLAGS_LUNNOTNEEDED|USBMSC_FLAGS_UACOKAY);
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_LUNNOTNEEDED |
|
||||||
|
USBMSC_FLAGS_UACOKAY);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
if (!priv->lun)
|
if (!priv->lun)
|
||||||
@ -657,7 +711,8 @@ static inline int usbmsc_cmdmodeselect6(FAR struct usbmsc_dev_s *priv)
|
|||||||
FAR struct scsicmd_modeselect6_s *modeselect = (FAR struct scsicmd_modeselect6_s *)priv->cdb;
|
FAR struct scsicmd_modeselect6_s *modeselect = (FAR struct scsicmd_modeselect6_s *)priv->cdb;
|
||||||
|
|
||||||
priv->u.alloclen = modeselect->plen;
|
priv->u.alloclen = modeselect->plen;
|
||||||
(void)usbmsc_setupcmd(priv, SCSICMD_MODESELECT6_SIZEOF, USBMSC_FLAGS_DIRHOST2DEVICE);
|
(void)usbmsc_setupcmd(priv, SCSICMD_MODESELECT6_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRHOST2DEVICE);
|
||||||
|
|
||||||
/* Not supported */
|
/* Not supported */
|
||||||
|
|
||||||
@ -739,7 +794,8 @@ static int inline usbmsc_cmdmodesense6(FAR struct usbmsc_dev_s *priv,
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.alloclen = modesense->alloclen;
|
priv->u.alloclen = modesense->alloclen;
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE6_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST);
|
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE6_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
if ((modesense->flags & ~SCSICMD_MODESENSE6_DBD) != 0 || modesense->subpgcode != 0)
|
if ((modesense->flags & ~SCSICMD_MODESENSE6_DBD) != 0 || modesense->subpgcode != 0)
|
||||||
@ -773,6 +829,7 @@ static int inline usbmsc_cmdmodesense6(FAR struct usbmsc_dev_s *priv,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,7 +846,8 @@ static inline int usbmsc_cmdstartstopunit(FAR struct usbmsc_dev_s *priv)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.alloclen = 0;
|
priv->u.alloclen = 0;
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_STARTSTOPUNIT_SIZEOF, USBMSC_FLAGS_DIRNONE);
|
ret = usbmsc_setupcmd(priv, SCSICMD_STARTSTOPUNIT_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRNONE);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_USBMSC_REMOVABLE
|
#ifndef CONFIG_USBMSC_REMOVABLE
|
||||||
@ -802,6 +860,7 @@ static inline int usbmsc_cmdstartstopunit(FAR struct usbmsc_dev_s *priv)
|
|||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -822,7 +881,8 @@ static inline int usbmsc_cmdpreventmediumremoval(FAR struct usbmsc_dev_s *priv)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.alloclen = 0;
|
priv->u.alloclen = 0;
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_PREVENTMEDIUMREMOVAL_SIZEOF, USBMSC_FLAGS_DIRNONE);
|
ret = usbmsc_setupcmd(priv, SCSICMD_PREVENTMEDIUMREMOVAL_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRNONE);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_USBMSC_REMOVABLE
|
#ifndef CONFIG_USBMSC_REMOVABLE
|
||||||
@ -839,6 +899,7 @@ static inline int usbmsc_cmdpreventmediumremoval(FAR struct usbmsc_dev_s *priv)
|
|||||||
lun->locked = pmr->prevent & SCSICMD_PREVENTMEDIUMREMOVAL_TRANSPORT;
|
lun->locked = pmr->prevent & SCSICMD_PREVENTMEDIUMREMOVAL_TRANSPORT;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -859,7 +920,8 @@ static inline int usbmsc_cmdreadformatcapacity(FAR struct usbmsc_dev_s *priv,
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.alloclen = usbmsc_getbe16(rfc->alloclen);
|
priv->u.alloclen = usbmsc_getbe16(rfc->alloclen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_READFORMATCAPACITIES_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST);
|
ret = usbmsc_setupcmd(priv, SCSICMD_READFORMATCAPACITIES_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
hdr = (FAR struct scsiresp_readformatcapacities_s *)buf;
|
hdr = (FAR struct scsiresp_readformatcapacities_s *)buf;
|
||||||
@ -873,6 +935,7 @@ static inline int usbmsc_cmdreadformatcapacity(FAR struct usbmsc_dev_s *priv,
|
|||||||
usbmsc_putbe24(hdr->blocklen, lun->sectorsize);
|
usbmsc_putbe24(hdr->blocklen, lun->sectorsize);
|
||||||
priv->nreqbytes = SCSIRESP_READFORMATCAPACITIES_SIZEOF;
|
priv->nreqbytes = SCSIRESP_READFORMATCAPACITIES_SIZEOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,7 +957,8 @@ static int inline usbmsc_cmdreadcapacity10(FAR struct usbmsc_dev_s *priv,
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.alloclen = SCSIRESP_READCAPACITY10_SIZEOF; /* Fake the allocation length */
|
priv->u.alloclen = SCSIRESP_READCAPACITY10_SIZEOF; /* Fake the allocation length */
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_READCAPACITY10_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST);
|
ret = usbmsc_setupcmd(priv, SCSICMD_READCAPACITY10_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Check the PMI and LBA fields */
|
/* Check the PMI and LBA fields */
|
||||||
@ -914,6 +978,7 @@ static int inline usbmsc_cmdreadcapacity10(FAR struct usbmsc_dev_s *priv,
|
|||||||
priv->nreqbytes = SCSIRESP_READCAPACITY10_SIZEOF;
|
priv->nreqbytes = SCSIRESP_READCAPACITY10_SIZEOF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -932,7 +997,8 @@ static inline int usbmsc_cmdread10(FAR struct usbmsc_dev_s *priv)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.xfrlen = usbmsc_getbe16(read10->xfrlen);
|
priv->u.xfrlen = usbmsc_getbe16(read10->xfrlen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_READ10_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST|USBMSC_FLAGS_BLOCKXFR);
|
ret = usbmsc_setupcmd(priv, SCSICMD_READ10_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_BLOCKXFR);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
||||||
@ -993,7 +1059,8 @@ static inline int usbmsc_cmdwrite10(FAR struct usbmsc_dev_s *priv)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.xfrlen = usbmsc_getbe16(write10->xfrlen);
|
priv->u.xfrlen = usbmsc_getbe16(write10->xfrlen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE10_SIZEOF, USBMSC_FLAGS_DIRHOST2DEVICE|USBMSC_FLAGS_BLOCKXFR);
|
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE10_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRHOST2DEVICE | USBMSC_FLAGS_BLOCKXFR);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
||||||
@ -1044,6 +1111,7 @@ static inline int usbmsc_cmdwrite10(FAR struct usbmsc_dev_s *priv)
|
|||||||
priv->thstate = USBMSC_STATE_CMDWRITE;
|
priv->thstate = USBMSC_STATE_CMDWRITE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1122,6 +1190,7 @@ static inline int usbmsc_cmdverify10(FAR struct usbmsc_dev_s *priv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1149,8 +1218,10 @@ static inline int usbmsc_cmdsynchronizecache10(FAR struct usbmsc_dev_s *priv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_SYNCHRONIZECACHE10_SIZEOF, USBMSC_FLAGS_DIRNONE);
|
ret = usbmsc_setupcmd(priv, SCSICMD_SYNCHRONIZECACHE10_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRNONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1167,7 +1238,8 @@ static inline int usbmsc_cmdmodeselect10(FAR struct usbmsc_dev_s *priv)
|
|||||||
FAR struct scsicmd_modeselect10_s *modeselect = (FAR struct scsicmd_modeselect10_s *)priv->cdb;
|
FAR struct scsicmd_modeselect10_s *modeselect = (FAR struct scsicmd_modeselect10_s *)priv->cdb;
|
||||||
|
|
||||||
priv->u.alloclen = usbmsc_getbe16(modeselect->parmlen);
|
priv->u.alloclen = usbmsc_getbe16(modeselect->parmlen);
|
||||||
(void)usbmsc_setupcmd(priv, SCSICMD_MODESELECT10_SIZEOF, USBMSC_FLAGS_DIRHOST2DEVICE);
|
(void)usbmsc_setupcmd(priv, SCSICMD_MODESELECT10_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRHOST2DEVICE);
|
||||||
|
|
||||||
/* Not supported */
|
/* Not supported */
|
||||||
|
|
||||||
@ -1192,10 +1264,12 @@ static int inline usbmsc_cmdmodesense10(FAR struct usbmsc_dev_s *priv,
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.alloclen = usbmsc_getbe16(modesense->alloclen);
|
priv->u.alloclen = usbmsc_getbe16(modesense->alloclen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE10_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST);
|
ret = usbmsc_setupcmd(priv, SCSICMD_MODESENSE10_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
if ((modesense->flags & ~SCSICMD_MODESENSE10_DBD) != 0 || modesense->subpgcode != 0)
|
if ((modesense->flags & ~SCSICMD_MODESENSE10_DBD) != 0 ||
|
||||||
|
modesense->subpgcode != 0)
|
||||||
{
|
{
|
||||||
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_MODESENSE10FLAGS), 0);
|
usbtrace(TRACE_CLSERROR(USBMSC_TRACEERR_MODESENSE10FLAGS), 0);
|
||||||
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
||||||
@ -1225,6 +1299,7 @@ static int inline usbmsc_cmdmodesense10(FAR struct usbmsc_dev_s *priv,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1243,7 +1318,8 @@ static inline int usbmsc_cmdread12(FAR struct usbmsc_dev_s *priv)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.xfrlen = usbmsc_getbe32(read12->xfrlen);
|
priv->u.xfrlen = usbmsc_getbe32(read12->xfrlen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_READ12_SIZEOF, USBMSC_FLAGS_DIRDEVICE2HOST|USBMSC_FLAGS_BLOCKXFR);
|
ret = usbmsc_setupcmd(priv, SCSICMD_READ12_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRDEVICE2HOST | USBMSC_FLAGS_BLOCKXFR);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
||||||
@ -1285,6 +1361,7 @@ static inline int usbmsc_cmdread12(FAR struct usbmsc_dev_s *priv)
|
|||||||
priv->thstate = USBMSC_STATE_CMDREAD;
|
priv->thstate = USBMSC_STATE_CMDREAD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1303,7 +1380,8 @@ static inline int usbmsc_cmdwrite12(FAR struct usbmsc_dev_s *priv)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
priv->u.xfrlen = usbmsc_getbe32(write12->xfrlen);
|
priv->u.xfrlen = usbmsc_getbe32(write12->xfrlen);
|
||||||
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE12_SIZEOF, USBMSC_FLAGS_DIRHOST2DEVICE|USBMSC_FLAGS_BLOCKXFR);
|
ret = usbmsc_setupcmd(priv, SCSICMD_WRITE12_SIZEOF,
|
||||||
|
USBMSC_FLAGS_DIRHOST2DEVICE | USBMSC_FLAGS_BLOCKXFR);
|
||||||
if (ret == OK)
|
if (ret == OK)
|
||||||
{
|
{
|
||||||
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
/* Get the Logical Block Address (LBA) from cdb[] as the starting sector */
|
||||||
@ -1364,20 +1442,21 @@ static inline int usbmsc_cmdwrite12(FAR struct usbmsc_dev_s *priv)
|
|||||||
* and verification operations that are common to all SCSI commands. This
|
* and verification operations that are common to all SCSI commands. This
|
||||||
* function performs the following common setup operations:
|
* function performs the following common setup operations:
|
||||||
*
|
*
|
||||||
* 1. Determine the direction of the response
|
* 1. Determine the direction of the response
|
||||||
* 2. Verify lengths
|
* 2. Verify lengths
|
||||||
* 3. Setup and verify the LUN
|
* 3. Setup and verify the LUN
|
||||||
*
|
*
|
||||||
* Includes special logic for INQUIRY and REQUESTSENSE commands
|
* Includes special logic for INQUIRY and REQUESTSENSE commands
|
||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
static int inline usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv, uint8_t cdblen, uint8_t flags)
|
static int inline usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv,
|
||||||
|
uint8_t cdblen, uint8_t flags)
|
||||||
{
|
{
|
||||||
FAR struct usbmsc_lun_s *lun = NULL;
|
FAR struct usbmsc_lun_s *lun = NULL;
|
||||||
uint32_t datlen;
|
uint32_t datlen;
|
||||||
uint8_t dir = flags & USBMSC_FLAGS_DIRMASK;
|
uint8_t dir = flags & USBMSC_FLAGS_DIRMASK;
|
||||||
int ret = OK;
|
int ret = OK;
|
||||||
|
|
||||||
/* Verify the LUN and set up the current LUN reference in the
|
/* Verify the LUN and set up the current LUN reference in the
|
||||||
* device structure
|
* device structure
|
||||||
@ -1469,7 +1548,7 @@ static int inline usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv, uint8_t cdblen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Compare the length of data in the cdb[] with the expected length
|
/* Compare the length of data in the cdb[] with the expected length
|
||||||
* of the command.
|
* of the command. These sizes should match exactly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (cdblen != priv->cdblen)
|
if (cdblen != priv->cdblen)
|
||||||
@ -1479,6 +1558,8 @@ static int inline usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv, uint8_t cdblen,
|
|||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Was a valid LUN provided? */
|
||||||
|
|
||||||
if (lun)
|
if (lun)
|
||||||
{
|
{
|
||||||
/* Retain the sense data for the REQUEST SENSE command */
|
/* Retain the sense data for the REQUEST SENSE command */
|
||||||
@ -1517,8 +1598,10 @@ static int inline usbmsc_setupcmd(FAR struct usbmsc_dev_s *priv, uint8_t cdblen,
|
|||||||
{
|
{
|
||||||
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA;
|
||||||
}
|
}
|
||||||
ret = -EINVAL;
|
|
||||||
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1724,7 +1807,7 @@ static int usbmsc_cmdparsestate(FAR struct usbmsc_dev_s *priv)
|
|||||||
|
|
||||||
/* Get exclusive access to the block driver */
|
/* Get exclusive access to the block driver */
|
||||||
|
|
||||||
pthread_mutex_lock(&priv->mutex);
|
usbmsc_scsi_lock(priv);
|
||||||
switch (priv->cdb[0])
|
switch (priv->cdb[0])
|
||||||
{
|
{
|
||||||
case SCSI_CMD_TESTUNITREADY: /* 0x00 Mandatory */
|
case SCSI_CMD_TESTUNITREADY: /* 0x00 Mandatory */
|
||||||
@ -1911,7 +1994,8 @@ static int usbmsc_cmdparsestate(FAR struct usbmsc_dev_s *priv)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
|
||||||
|
usbmsc_scsi_unlock(priv);
|
||||||
|
|
||||||
/* Is a response required? (Not for read6/10/12 and write6/10/12). */
|
/* Is a response required? (Not for read6/10/12 and write6/10/12). */
|
||||||
|
|
||||||
@ -2520,7 +2604,7 @@ static int usbmsc_cmdstatusstate(FAR struct usbmsc_dev_s *priv)
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Name: usbmsc_workerthread
|
* Name: usbmsc_scsi_main
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* This is the main function of the USB storage worker thread. It loops
|
* This is the main function of the USB storage worker thread. It loops
|
||||||
@ -2528,31 +2612,44 @@ static int usbmsc_cmdstatusstate(FAR struct usbmsc_dev_s *priv)
|
|||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
void *usbmsc_workerthread(void *arg)
|
int usbmsc_scsi_main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct usbmsc_dev_s *priv = (struct usbmsc_dev_s *)arg;
|
FAR struct usbmsc_dev_s *priv;
|
||||||
irqstate_t flags;
|
irqstate_t flags;
|
||||||
uint16_t eventset;
|
uint16_t eventset;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
uvdbg("Started\n");
|
||||||
|
|
||||||
|
/* Get the SCSI state data handed off from the initialization logic */
|
||||||
|
|
||||||
|
priv = g_usbmsc_handoff;
|
||||||
|
DEBUGASSERT(priv);
|
||||||
|
|
||||||
|
g_usbmsc_handoff = NULL;
|
||||||
|
usbmsc_synch_signal(priv);
|
||||||
|
|
||||||
/* This thread is started before the USB storage class is fully initialized.
|
/* This thread is started before the USB storage class is fully initialized.
|
||||||
* wait here until we are told to begin. Start in the NOTINITIALIZED state
|
* wait here until we are told to begin. Start in the NOTINITIALIZED state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pthread_mutex_lock(&priv->mutex);
|
uvdbg("Waiting to be signalled\n");
|
||||||
|
usbmsc_scsi_lock(priv);
|
||||||
priv->thstate = USBMSC_STATE_STARTED;
|
priv->thstate = USBMSC_STATE_STARTED;
|
||||||
while ((priv->theventset & USBMSC_EVENT_READY) != 0 &&
|
while ((priv->theventset & USBMSC_EVENT_READY) != 0 &&
|
||||||
(priv->theventset & USBMSC_EVENT_TERMINATEREQUEST) != 0)
|
(priv->theventset & USBMSC_EVENT_TERMINATEREQUEST) != 0)
|
||||||
{
|
{
|
||||||
pthread_cond_wait(&priv->cond, &priv->mutex);
|
usbmsc_scsi_wait(priv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uvdbg("Running\n");
|
||||||
|
|
||||||
/* Transition to the INITIALIZED/IDLE state */
|
/* Transition to the INITIALIZED/IDLE state */
|
||||||
|
|
||||||
priv->thstate = USBMSC_STATE_IDLE;
|
priv->thstate = USBMSC_STATE_IDLE;
|
||||||
eventset = priv->theventset;
|
eventset = priv->theventset;
|
||||||
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
usbmsc_scsi_unlock(priv);
|
||||||
|
|
||||||
/* Then loop until we are asked to terminate */
|
/* Then loop until we are asked to terminate */
|
||||||
|
|
||||||
@ -2563,11 +2660,11 @@ void *usbmsc_workerthread(void *arg)
|
|||||||
* interrupts (to eliminate race conditions with USB interrupt handling.
|
* interrupts (to eliminate race conditions with USB interrupt handling.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pthread_mutex_lock(&priv->mutex);
|
usbmsc_scsi_lock(priv);
|
||||||
flags = irqsave();
|
flags = irqsave();
|
||||||
if (priv->theventset == USBMSC_EVENT_NOEVENTS)
|
if (priv->theventset == USBMSC_EVENT_NOEVENTS)
|
||||||
{
|
{
|
||||||
pthread_cond_wait(&priv->cond, &priv->mutex);
|
usbmsc_scsi_wait(priv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sample any events before re-enabling interrupts. Any events that
|
/* Sample any events before re-enabling interrupts. Any events that
|
||||||
@ -2577,7 +2674,7 @@ void *usbmsc_workerthread(void *arg)
|
|||||||
|
|
||||||
eventset = priv->theventset;
|
eventset = priv->theventset;
|
||||||
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
priv->theventset = USBMSC_EVENT_NOEVENTS;
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
usbmsc_scsi_unlock(priv);
|
||||||
|
|
||||||
/* Were we awakened by some event that requires immediate action?
|
/* Were we awakened by some event that requires immediate action?
|
||||||
*
|
*
|
||||||
@ -2614,7 +2711,7 @@ void *usbmsc_workerthread(void *arg)
|
|||||||
|
|
||||||
/* These events require that a new configuration be established */
|
/* These events require that a new configuration be established */
|
||||||
|
|
||||||
if ((eventset & (USBMSC_EVENT_CFGCHANGE|USBMSC_EVENT_IFCHANGE)) != 0)
|
if ((eventset & (USBMSC_EVENT_CFGCHANGE)) != 0)
|
||||||
{
|
{
|
||||||
usbmsc_setconfig(priv, priv->thvalue);
|
usbmsc_setconfig(priv, priv->thvalue);
|
||||||
}
|
}
|
||||||
@ -2689,8 +2786,54 @@ void *usbmsc_workerthread(void *arg)
|
|||||||
/* Transition to the TERMINATED state and exit */
|
/* Transition to the TERMINATED state and exit */
|
||||||
|
|
||||||
priv->thstate = USBMSC_STATE_TERMINATED;
|
priv->thstate = USBMSC_STATE_TERMINATED;
|
||||||
pthread_mutex_lock(&priv->mutex); /* REVISIT: See comments in usbmsc_uninitialize() */
|
usbmsc_synch_signal(priv);
|
||||||
pthread_cond_signal(&priv->cond);
|
return EXIT_SUCCESS;
|
||||||
pthread_mutex_unlock(&priv->mutex);
|
}
|
||||||
return NULL;
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: usbmsc_scsi_signal
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Signal the SCSI worker thread that SCSI events need service.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
void usbmsc_scsi_signal(FAR struct usbmsc_dev_s *priv)
|
||||||
|
{
|
||||||
|
irqstate_t flags = irqsave();
|
||||||
|
|
||||||
|
/* A flag is used to prevent driving up the semaphore count. This function
|
||||||
|
* is called (primarily) from interrupt level logic so we must disable
|
||||||
|
* interrupts momentarily to assure that test of the flag and the increment
|
||||||
|
* of the semaphore count are atomic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
flags = irqsave();
|
||||||
|
if (priv->thwaiting)
|
||||||
|
{
|
||||||
|
priv->thwaiting = false;
|
||||||
|
sem_post(&priv->thwaitsem);
|
||||||
|
}
|
||||||
|
|
||||||
|
irqrestore(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: usbmsc_scsi_lock
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Get exclusive access to SCSI state data.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
void usbmsc_scsi_lock(FAR struct usbmsc_dev_s *priv)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = sem_wait(&priv->thlock);
|
||||||
|
DEBUGASSERT(ret == OK || errno == EINTR);
|
||||||
|
}
|
||||||
|
while (ret < 0);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user