ahci: NCQ support

This commit is contained in:
Raja Appuswamy 2011-12-07 15:44:28 +01:00 committed by David van Moolenbroek
parent 94da86cbee
commit 8ab7667da4
14 changed files with 615 additions and 398 deletions

View file

@ -3,6 +3,7 @@
#include <minix/driver.h> #include <minix/driver.h>
typedef int device_id_t;
typedef int thread_id_t; typedef int thread_id_t;
/* Types supported for the 'type' field of struct blockdriver. */ /* Types supported for the 'type' field of struct blockdriver. */
@ -26,7 +27,7 @@ struct blockdriver {
_PROTOTYPE( void (*bdr_intr), (unsigned int irqs) ); _PROTOTYPE( void (*bdr_intr), (unsigned int irqs) );
_PROTOTYPE( void (*bdr_alarm), (clock_t stamp) ); _PROTOTYPE( void (*bdr_alarm), (clock_t stamp) );
_PROTOTYPE( int (*bdr_other), (message *m_ptr) ); _PROTOTYPE( int (*bdr_other), (message *m_ptr) );
_PROTOTYPE( int (*bdr_thread), (dev_t minor, thread_id_t *threadp) ); _PROTOTYPE( int (*bdr_device), (dev_t minor, device_id_t *id) );
}; };
/* Functions defined by libblockdriver. These can be used for both /* Functions defined by libblockdriver. These can be used for both

View file

@ -4,13 +4,11 @@
#define BLOCKDRIVER_MT_API 1 /* do not expose the singlethreaded API */ #define BLOCKDRIVER_MT_API 1 /* do not expose the singlethreaded API */
#include <minix/blockdriver.h> #include <minix/blockdriver.h>
/* The maximum number of worker threads. */
#define BLOCKDRIVER_MT_MAX_WORKERS 32
_PROTOTYPE( void blockdriver_mt_task, (struct blockdriver *driver_tab) ); _PROTOTYPE( void blockdriver_mt_task, (struct blockdriver *driver_tab) );
_PROTOTYPE( void blockdriver_mt_sleep, (void) ); _PROTOTYPE( void blockdriver_mt_sleep, (void) );
_PROTOTYPE( void blockdriver_mt_wakeup, (thread_id_t id) ); _PROTOTYPE( void blockdriver_mt_wakeup, (thread_id_t id) );
_PROTOTYPE( void blockdriver_mt_stop, (void) );
_PROTOTYPE( void blockdriver_mt_terminate, (void) ); _PROTOTYPE( void blockdriver_mt_terminate, (void) );
_PROTOTYPE( void blockdriver_mt_set_workers, (device_id_t id, int workers) );
_PROTOTYPE( thread_id_t blockdriver_mt_get_tid, (void) );
#endif /* _MINIX_BLOCKDRIVER_MT_H */ #endif /* _MINIX_BLOCKDRIVER_MT_H */

File diff suppressed because it is too large Load diff

View file

@ -4,13 +4,13 @@
#include <minix/drivers.h> #include <minix/drivers.h>
#define NR_PORTS 32 /* maximum number of ports */ #define NR_PORTS 32 /* maximum number of ports */
#define NR_CMDS 1 /* maximum number of queued commands */ #define NR_CMDS 32 /* maximum number of queued commands */
/* Time values that can be set with options. */ /* Time values that can be set with options. */
#define SPINUP_TIMEOUT 5000 /* initial spin-up time (ms) */ #define SPINUP_TIMEOUT 5000 /* initial spin-up time (ms) */
#define SIG_TIMEOUT 250 /* time between signature checks (ms) */ #define SIG_TIMEOUT 250 /* time between signature checks (ms) */
#define NR_SIG_CHECKS 60 /* maximum number of times to check */ #define NR_SIG_CHECKS 60 /* maximum number of times to check */
#define COMMAND_TIMEOUT 5000 /* time to wait for non-I/O cmd (ms) */ #define COMMAND_TIMEOUT 10000 /* time to wait for non-I/O cmd (ms) */
#define TRANSFER_TIMEOUT 30000 /* time to wait for I/O cmd (ms) */ #define TRANSFER_TIMEOUT 30000 /* time to wait for I/O cmd (ms) */
#define FLUSH_TIMEOUT 60000 /* time to wait for flush cmd (ms) */ #define FLUSH_TIMEOUT 60000 /* time to wait for flush cmd (ms) */
@ -30,6 +30,8 @@
#define ATA_H2D_CMD 2 /* Command */ #define ATA_H2D_CMD 2 /* Command */
#define ATA_CMD_READ_DMA_EXT 0x25 /* READ DMA EXT */ #define ATA_CMD_READ_DMA_EXT 0x25 /* READ DMA EXT */
#define ATA_CMD_WRITE_DMA_EXT 0x35 /* WRITE DMA EXT */ #define ATA_CMD_WRITE_DMA_EXT 0x35 /* WRITE DMA EXT */
#define ATA_CMD_READ_FPDMA_QUEUED 0x60 /* READ FPDMA QUEUED */
#define ATA_CMD_WRITE_FPDMA_QUEUED 0x61 /* WRITE FPDMA QUEUED */
#define ATA_CMD_WRITE_DMA_FUA_EXT 0x3D /* WRITE DMA FUA EXT */ #define ATA_CMD_WRITE_DMA_FUA_EXT 0x3D /* WRITE DMA FUA EXT */
#define ATA_CMD_PACKET 0xA0 /* PACKET */ #define ATA_CMD_PACKET 0xA0 /* PACKET */
#define ATA_CMD_IDENTIFY_PACKET 0xA1 /* IDENTIFY PACKET DEVICE */ #define ATA_CMD_IDENTIFY_PACKET 0xA1 /* IDENTIFY PACKET DEVICE */
@ -44,14 +46,20 @@
#define ATA_H2D_LBA_HIGH 6 /* LBA High */ #define ATA_H2D_LBA_HIGH 6 /* LBA High */
#define ATA_H2D_DEV 7 /* Device */ #define ATA_H2D_DEV 7 /* Device */
#define ATA_DEV_LBA 0x40 /* use LBA addressing */ #define ATA_DEV_LBA 0x40 /* use LBA addressing */
#define ATA_DEV_FUA 0x80 /* Force Unit Access (FPDMA) */
#define ATA_H2D_LBA_LOW_EXP 8 /* LBA Low (exp) */ #define ATA_H2D_LBA_LOW_EXP 8 /* LBA Low (exp) */
#define ATA_H2D_LBA_MID_EXP 9 /* LBA Mid (exp) */ #define ATA_H2D_LBA_MID_EXP 9 /* LBA Mid (exp) */
#define ATA_H2D_LBA_HIGH_EXP 10 /* LBA High (exp) */ #define ATA_H2D_LBA_HIGH_EXP 10 /* LBA High (exp) */
#define ATA_H2D_FEAT_EXP 11 /* Features (exp) */ #define ATA_H2D_FEAT_EXP 11 /* Features (exp) */
#define ATA_H2D_SEC 12 /* Sector Count */ #define ATA_H2D_SEC 12 /* Sector Count */
#define ATA_SEC_TAG_SHIFT 3 /* NCQ command tag */
#define ATA_H2D_SEC_EXP 13 /* Sector Count (exp) */ #define ATA_H2D_SEC_EXP 13 /* Sector Count (exp) */
#define ATA_H2D_CTL 15 /* Control */ #define ATA_H2D_CTL 15 /* Control */
#define ATA_IS_FPDMA_CMD(c) \
((c) == ATA_CMD_READ_FPDMA_QUEUED || \
(c) == ATA_CMD_WRITE_FPDMA_QUEUED)
/* ATA constants. */ /* ATA constants. */
#define ATA_SECTOR_SIZE 512 /* default sector size */ #define ATA_SECTOR_SIZE 512 /* default sector size */
#define ATA_MAX_SECTORS 0x10000 /* max sectors per transfer */ #define ATA_MAX_SECTORS 0x10000 /* max sectors per transfer */
@ -73,6 +81,10 @@
#define ATA_ID_DMADIR 62 /* DMADIR */ #define ATA_ID_DMADIR 62 /* DMADIR */
#define ATA_ID_DMADIR_DMADIR 0x8000 /* DMADIR required */ #define ATA_ID_DMADIR_DMADIR 0x8000 /* DMADIR required */
#define ATA_ID_DMADIR_DMA 0x0400 /* DMA supported (DMADIR) */ #define ATA_ID_DMADIR_DMA 0x0400 /* DMA supported (DMADIR) */
#define ATA_ID_QDEPTH 75 /* NCQ queue depth */
#define ATA_ID_QDEPTH_MASK 0x000F /* NCQ queue depth mask */
#define ATA_ID_SATA_CAP 76 /* SATA capabilities */
#define ATA_ID_SATA_CAP_NCQ 0x0100 /* NCQ support */
#define ATA_ID_SUP0 82 /* Features supported (1/3) */ #define ATA_ID_SUP0 82 /* Features supported (1/3) */
#define ATA_ID_SUP0_WCACHE 0x0020 /* Write cache supported */ #define ATA_ID_SUP0_WCACHE 0x0020 /* Write cache supported */
#define ATA_ID_SUP1 83 /* Features supported (2/3) */ #define ATA_ID_SUP1 83 /* Features supported (2/3) */
@ -160,6 +172,7 @@
#define AHCI_PORT_IS_IFS (1L << 27) /* Interface Fatal */ #define AHCI_PORT_IS_IFS (1L << 27) /* Interface Fatal */
#define AHCI_PORT_IS_PRCS (1L << 22) /* PhyRdy Change */ #define AHCI_PORT_IS_PRCS (1L << 22) /* PhyRdy Change */
#define AHCI_PORT_IS_PCS (1L << 6) /* Port Conn Change */ #define AHCI_PORT_IS_PCS (1L << 6) /* Port Conn Change */
#define AHCI_PORT_IS_SDBS (1L << 3) /* Set Device Bits FIS */
#define AHCI_PORT_IS_PSS (1L << 1) /* PIO Setup FIS */ #define AHCI_PORT_IS_PSS (1L << 1) /* PIO Setup FIS */
#define AHCI_PORT_IS_DHRS (1L << 0) /* D2H Register FIS */ #define AHCI_PORT_IS_DHRS (1L << 0) /* D2H Register FIS */
#define AHCI_PORT_IS_RESTART \ #define AHCI_PORT_IS_RESTART \
@ -167,7 +180,7 @@
AHCI_PORT_IS_IFS) AHCI_PORT_IS_IFS)
#define AHCI_PORT_IS_MASK \ #define AHCI_PORT_IS_MASK \
(AHCI_PORT_IS_RESTART | AHCI_PORT_IS_PRCS | AHCI_PORT_IS_PCS | \ (AHCI_PORT_IS_RESTART | AHCI_PORT_IS_PRCS | AHCI_PORT_IS_PCS | \
AHCI_PORT_IS_DHRS | AHCI_PORT_IS_PSS) AHCI_PORT_IS_DHRS | AHCI_PORT_IS_PSS | AHCI_PORT_IS_SDBS)
#define AHCI_PORT_IE 5 /* Interrupt Enable */ #define AHCI_PORT_IE 5 /* Interrupt Enable */
#define AHCI_PORT_IE_MASK AHCI_PORT_IS_MASK #define AHCI_PORT_IE_MASK AHCI_PORT_IS_MASK
#define AHCI_PORT_IE_PRCE AHCI_PORT_IS_PRCS #define AHCI_PORT_IE_PRCE AHCI_PORT_IS_PRCS
@ -196,6 +209,7 @@
#define AHCI_PORT_SCTL_DET_NONE 0x00000000L /* No Action Req'd */ #define AHCI_PORT_SCTL_DET_NONE 0x00000000L /* No Action Req'd */
#define AHCI_PORT_SERR 12 /* Serial ATA Error */ #define AHCI_PORT_SERR 12 /* Serial ATA Error */
#define AHCI_PORT_SERR_DIAG_N (1L << 16) /* PhyRdy Change */ #define AHCI_PORT_SERR_DIAG_N (1L << 16) /* PhyRdy Change */
#define AHCI_PORT_SACT 13 /* Serial ATA Active */
#define AHCI_PORT_CI 14 /* Command Issue */ #define AHCI_PORT_CI 14 /* Command Issue */
/* Number of Physical Region Descriptors (PRDs). Must be at least NR_IOREQS+2, /* Number of Physical Region Descriptors (PRDs). Must be at least NR_IOREQS+2,
@ -262,6 +276,12 @@ enum {
STATE_GOOD_DEV /* a usable device has been detected */ STATE_GOOD_DEV /* a usable device has been detected */
}; };
/* Command results. */
enum {
RESULT_FAILURE,
RESULT_SUCCESS
};
/* Port flags. */ /* Port flags. */
#define FLAG_ATAPI 0x00000001 /* is this an ATAPI device? */ #define FLAG_ATAPI 0x00000001 /* is this an ATAPI device? */
#define FLAG_HAS_MEDIUM 0x00000002 /* is a medium present? */ #define FLAG_HAS_MEDIUM 0x00000002 /* is a medium present? */
@ -274,6 +294,8 @@ enum {
#define FLAG_HAS_FLUSH 0x00000100 /* is FLUSH CACHE supported? */ #define FLAG_HAS_FLUSH 0x00000100 /* is FLUSH CACHE supported? */
#define FLAG_SUSPENDED 0x00000200 /* is the thread suspended? */ #define FLAG_SUSPENDED 0x00000200 /* is the thread suspended? */
#define FLAG_HAS_FUA 0x00000400 /* is WRITE DMA FUA EX sup.? */ #define FLAG_HAS_FUA 0x00000400 /* is WRITE DMA FUA EX sup.? */
#define FLAG_HAS_NCQ 0x00000800 /* is NCQ supported? */
#define FLAG_NCQ_MODE 0x00001000 /* issuing NCQ commands? */
/* Mapping between devices and ports. */ /* Mapping between devices and ports. */
#define NO_PORT -1 /* this device maps to no port */ #define NO_PORT -1 /* this device maps to no port */

View file

@ -3,7 +3,7 @@
LIB= blockdriver LIB= blockdriver
SRCS= driver.c drvlib.c driver_st.c driver_mt.c mq.c event.c trace.c SRCS= driver.c drvlib.c driver_st.c driver_mt.c mq.c trace.c
.if ${USE_STATECTL} != "no" .if ${USE_STATECTL} != "no"
CPPFLAGS+= -DUSE_STATECTL CPPFLAGS+= -DUSE_STATECTL

View file

@ -0,0 +1,14 @@
#ifndef _BLOCKDRIVER_CONST_H
#define _BLOCKDRIVER_CONST_H
/* Maximum number of devices supported. */
#define MAX_DEVICES 32
/* The maximum number of worker threads per device. */
#define MAX_WORKERS 32
#define MAX_THREADS (MAX_DEVICES * MAX_WORKERS) /* max nr of threads */
#define MAIN_THREAD (MAX_THREADS) /* main thread ID */
#define SINGLE_THREAD (0) /* single-thread ID */
#endif /* _BLOCKDRIVER_CONST_H */

View file

@ -1,9 +1,6 @@
#ifndef _BLOCKDRIVER_DRIVER_H #ifndef _BLOCKDRIVER_DRIVER_H
#define _BLOCKDRIVER_DRIVER_H #define _BLOCKDRIVER_DRIVER_H
#define SINGLE_THREAD (0) /* single-thread ID */
#define MAIN_THREAD (BLOCKDRIVER_MT_MAX_WORKERS) /* main thread ID */
_PROTOTYPE( void blockdriver_handle_notify, (struct blockdriver *bdp, _PROTOTYPE( void blockdriver_handle_notify, (struct blockdriver *bdp,
message *m_ptr) ); message *m_ptr) );
_PROTOTYPE( int blockdriver_handle_request, (struct blockdriver *bdp, _PROTOTYPE( int blockdriver_handle_request, (struct blockdriver *bdp,

View file

@ -8,84 +8,132 @@
* blockdriver_mt_terminate: break out of the main message loop * blockdriver_mt_terminate: break out of the main message loop
* blockdriver_mt_sleep: put the current thread to sleep * blockdriver_mt_sleep: put the current thread to sleep
* blockdriver_mt_wakeup: wake up a sleeping thread * blockdriver_mt_wakeup: wake up a sleeping thread
* blockdriver_mt_stop: put up the current thread for termination * blockdriver_mt_set_workers:set the number of worker threads
*/ */
#include <minix/blockdriver_mt.h> #include <minix/blockdriver_mt.h>
#include <minix/mthread.h> #include <minix/mthread.h>
#include <assert.h> #include <assert.h>
#include "const.h"
#include "driver.h" #include "driver.h"
#include "mq.h" #include "mq.h"
#include "event.h"
/* A thread ID is composed of a device ID and a per-device worker thread ID.
* All thread IDs must be in the range 0..(MAX_THREADS-1) inclusive.
*/
#define MAKE_TID(did, wid) ((did) * MAX_WORKERS + (wid))
#define TID_DEVICE(tid) ((tid) / MAX_WORKERS)
#define TID_WORKER(tid) ((tid) % MAX_WORKERS)
typedef int worker_id_t;
typedef enum { typedef enum {
STATE_DEAD, STATE_DEAD,
STATE_RUNNING, STATE_RUNNING,
STATE_STOPPING, STATE_BUSY,
STATE_EXITED STATE_EXITED
} worker_state; } worker_state;
/* Structure to handle running worker threads. */ /* Structure with information about a worker thread. */
typedef struct { typedef struct {
thread_id_t id; device_id_t device_id;
worker_id_t worker_id;
worker_state state; worker_state state;
mthread_thread_t mthread; mthread_thread_t mthread;
event_t queue_event; mthread_event_t sleep_event;
event_t sleep_event;
} worker_t; } worker_t;
/* Structure with information about a device. */
typedef struct {
device_id_t id;
unsigned int workers;
worker_t worker[MAX_WORKERS];
mthread_event_t queue_event;
mthread_rwlock_t barrier;
} device_t;
PRIVATE struct blockdriver *bdtab; PRIVATE struct blockdriver *bdtab;
PRIVATE int running = FALSE; PRIVATE int running = FALSE;
PRIVATE mthread_key_t worker_key; PRIVATE mthread_key_t worker_key;
PRIVATE worker_t worker[BLOCKDRIVER_MT_MAX_WORKERS]; PRIVATE device_t device[MAX_DEVICES];
PRIVATE worker_t *exited[BLOCKDRIVER_MT_MAX_WORKERS]; PRIVATE worker_t *exited[MAX_THREADS];
PRIVATE int num_exited = 0; PRIVATE int num_exited = 0;
/*===========================================================================* /*===========================================================================*
* enqueue * * enqueue *
*===========================================================================*/ *===========================================================================*/
PRIVATE void enqueue(worker_t *wp, const message *m_src, int ipc_status) PRIVATE void enqueue(device_t *dp, const message *m_src, int ipc_status)
{ {
/* Enqueue a message into a worker thread's queue, and signal the thread. /* Enqueue a message into the device's queue, and signal the event.
* Must be called from the master thread. * Must be called from the master thread.
*/ */
assert(wp->state == STATE_RUNNING || wp->state == STATE_STOPPING); if (!mq_enqueue(dp->id, m_src, ipc_status))
if (!mq_enqueue(wp->id, m_src, ipc_status))
panic("blockdriver_mt: enqueue failed (message queue full)"); panic("blockdriver_mt: enqueue failed (message queue full)");
event_fire(&wp->queue_event); mthread_event_fire(&dp->queue_event);
} }
/*===========================================================================* /*===========================================================================*
* try_dequeue * * try_dequeue *
*===========================================================================*/ *===========================================================================*/
PRIVATE int try_dequeue(worker_t *wp, message *m_dst, int *ipc_status) PRIVATE int try_dequeue(device_t *dp, message *m_dst, int *ipc_status)
{ {
/* See if a message can be dequeued from the current worker thread's queue. If /* See if a message can be dequeued from the current worker thread's device
* so, dequeue the message and return TRUE. If not, return FALSE. Must be * queue. If so, dequeue the message and return TRUE. If not, return FALSE.
* called from a worker thread. Does not block. * Must be called from a worker thread. Does not block.
*/ */
return mq_dequeue(wp->id, m_dst, ipc_status); return mq_dequeue(dp->id, m_dst, ipc_status);
} }
/*===========================================================================* /*===========================================================================*
* dequeue * * dequeue *
*===========================================================================*/ *===========================================================================*/
PRIVATE void dequeue(worker_t *wp, message *m_dst, int *ipc_status) PRIVATE int dequeue(device_t *dp, worker_t *wp, message *m_dst,
int *ipc_status)
{ {
/* Dequeue a message from the current worker thread's queue. Block the current /* Dequeue a message from the current worker thread's device queue. Block the
* thread if necessary. Must be called from a worker thread. Always successful. * current thread if necessary. Must be called from a worker thread. Either
* succeeds with a message (TRUE) or indicates that the thread should be
* terminated (FALSE).
*/ */
while (!try_dequeue(wp, m_dst, ipc_status)) do {
event_wait(&wp->queue_event); mthread_event_wait(&dp->queue_event);
/* If we were woken up as a result of terminate or set_workers, break
* out of the loop and terminate the thread.
*/
if (!running || wp->worker_id >= dp->workers)
return FALSE;
} while (!try_dequeue(dp, m_dst, ipc_status));
return TRUE;
}
/*===========================================================================*
* is_transfer_req *
*===========================================================================*/
PRIVATE int is_transfer_req(int type)
{
/* Return whether the given block device request is a transfer request.
*/
switch (type) {
case BDEV_READ:
case BDEV_WRITE:
case BDEV_GATHER:
case BDEV_SCATTER:
return TRUE;
default:
return FALSE;
}
} }
/*===========================================================================* /*===========================================================================*
@ -99,36 +147,50 @@ PRIVATE void *worker_thread(void *param)
* for this condition and exit if so. * for this condition and exit if so.
*/ */
worker_t *wp; worker_t *wp;
device_t *dp;
thread_id_t tid;
message m; message m;
int ipc_status, r; int ipc_status, r;
wp = (worker_t *) param; wp = (worker_t *) param;
assert(wp != NULL); assert(wp != NULL);
dp = &device[wp->device_id];
tid = MAKE_TID(wp->device_id, wp->worker_id);
if (mthread_setspecific(worker_key, wp)) if (mthread_setspecific(worker_key, wp))
panic("blockdriver_mt: could not save local thread pointer"); panic("blockdriver_mt: could not save local thread pointer");
while (running) { while (running && wp->worker_id < dp->workers) {
/* See if a new message is available right away. */ /* See if a new message is available right away. */
if (!try_dequeue(wp, &m, &ipc_status)) { if (!try_dequeue(dp, &m, &ipc_status)) {
/* If not, and this thread should be stopped, stop now. */
if (wp->state == STATE_STOPPING)
break;
/* Otherwise, block waiting for a new message. */ /* If not, block waiting for a new message or a thread
dequeue(wp, &m, &ipc_status); * termination event.
*/
if (!running) if (!dequeue(dp, wp, &m, &ipc_status))
break; break;
} }
/* Even if the thread was stopped before, a new message resumes it. */ /* Even if the thread was stopped before, a new message resumes it. */
wp->state = STATE_RUNNING; wp->state = STATE_BUSY;
/* Handle the request, and send a reply. */ /* If the request is a transfer request, we acquire the read barrier
r = blockdriver_handle_request(bdtab, &m, wp->id); * lock. Otherwise, we acquire the write lock.
*/
if (is_transfer_req(m.m_type))
mthread_rwlock_rdlock(&dp->barrier);
else
mthread_rwlock_wrlock(&dp->barrier);
/* Handle the request and send a reply. */
r = blockdriver_handle_request(bdtab, &m, tid);
blockdriver_reply(&m, ipc_status, r); blockdriver_reply(&m, ipc_status, r);
/* Switch the thread back to running state, and unlock the barrier. */
wp->state = STATE_RUNNING;
mthread_rwlock_unlock(&dp->barrier);
} }
/* Clean up and terminate this thread. */ /* Clean up and terminate this thread. */
@ -145,23 +207,24 @@ PRIVATE void *worker_thread(void *param)
/*===========================================================================* /*===========================================================================*
* master_create_worker * * master_create_worker *
*===========================================================================*/ *===========================================================================*/
PRIVATE void master_create_worker(worker_t *wp, thread_id_t id) PRIVATE void master_create_worker(worker_t *wp, worker_id_t worker_id,
device_id_t device_id)
{ {
/* Start a new worker thread. /* Start a new worker thread.
*/ */
int r; int r;
wp->id = id; wp->device_id = device_id;
wp->worker_id = worker_id;
wp->state = STATE_RUNNING; wp->state = STATE_RUNNING;
/* Initialize synchronization primitives. */ /* Initialize synchronization primitives. */
event_init(&wp->queue_event); mthread_event_init(&wp->sleep_event);
event_init(&wp->sleep_event);
r = mthread_create(&wp->mthread, NULL /*attr*/, worker_thread, (void *) wp); r = mthread_create(&wp->mthread, NULL /*attr*/, worker_thread, (void *) wp);
if (r != 0) if (r != 0)
panic("blockdriver_mt: could not start thread %d (%d)", id, r); panic("blockdriver_mt: could not start thread %d (%d)", worker_id, r);
} }
/*===========================================================================* /*===========================================================================*
@ -171,20 +234,16 @@ PRIVATE void master_destroy_worker(worker_t *wp)
{ {
/* Clean up resources used by an exited worker thread. /* Clean up resources used by an exited worker thread.
*/ */
message m;
int ipc_status;
assert(wp != NULL); assert(wp != NULL);
assert(wp->state == STATE_EXITED); assert(wp->state == STATE_EXITED);
assert(!mq_dequeue(wp->id, &m, &ipc_status));
/* Join the thread. */ /* Join the thread. */
if (mthread_join(wp->mthread, NULL)) if (mthread_join(wp->mthread, NULL))
panic("blockdriver_mt: could not join thread %d", wp->id); panic("blockdriver_mt: could not join thread %d", wp->worker_id);
/* Destroy resources. */ /* Destroy resources. */
event_destroy(&wp->sleep_event); mthread_event_destroy(&wp->sleep_event);
event_destroy(&wp->queue_event);
wp->state = STATE_DEAD; wp->state = STATE_DEAD;
} }
@ -209,13 +268,14 @@ PRIVATE void master_handle_exits(void)
*===========================================================================*/ *===========================================================================*/
PRIVATE void master_handle_request(message *m_ptr, int ipc_status) PRIVATE void master_handle_request(message *m_ptr, int ipc_status)
{ {
/* For real request messages, query the thread ID, start a thread if one with /* For real request messages, query the device ID, start a thread if none is
* that ID is not already running, and enqueue the message in the thread's * free and the maximum number of threads for that device has not yet been
* message queue. * reached, and enqueue the message in the devices's message queue.
*/ */
thread_id_t thread_id; device_id_t id;
worker_t *wp; worker_t *wp;
int r; device_t *dp;
int r, wid;
/* If this is not a block driver request, we cannot get the minor device /* If this is not a block driver request, we cannot get the minor device
* associated with it, and thus we can not tell which thread should process * associated with it, and thus we can not tell which thread should process
@ -230,27 +290,38 @@ PRIVATE void master_handle_request(message *m_ptr, int ipc_status)
return; return;
} }
/* Query the thread ID. Upon failure, send the error code to the caller. */ /* Query the device ID. Upon failure, send the error code to the caller. */
r = (*bdtab->bdr_thread)(m_ptr->DEVICE, &thread_id); r = (*bdtab->bdr_device)(m_ptr->BDEV_MINOR, &id);
if (r != OK) { if (r != OK) {
blockdriver_reply(m_ptr, ipc_status, r); blockdriver_reply(m_ptr, ipc_status, r);
return; return;
} }
/* Start the thread if it is not already running. */ /* Look up the device control block. */
assert(thread_id >= 0 && thread_id < BLOCKDRIVER_MT_MAX_WORKERS); assert(id >= 0 && id < MAX_DEVICES);
dp = &device[id];
wp = &worker[thread_id]; /* Find the first non-busy worker thread. */
for (wid = 0; wid < dp->workers; wid++)
if (dp->worker[wid].state != STATE_BUSY)
break;
assert(wp->state != STATE_EXITED); /* If the worker thread is dead, start a thread now, unless we have already
* reached the maximum number of threads.
*/
if (wid < dp->workers) {
wp = &dp->worker[wid];
if (wp->state == STATE_DEAD) assert(wp->state != STATE_EXITED);
master_create_worker(wp, thread_id);
/* Enqueue the message for the thread, and possibly wake it up. */ /* If the non-busy thread has not yet been created, create one now. */
enqueue(wp, m_ptr, ipc_status); if (wp->state == STATE_DEAD)
master_create_worker(wp, wid, dp->id);
}
/* Enqueue the message at the device queue. */
enqueue(dp, m_ptr, ipc_status);
} }
/*===========================================================================* /*===========================================================================*
@ -260,17 +331,25 @@ PRIVATE void master_init(struct blockdriver *bdp)
{ {
/* Initialize the state of the master thread. /* Initialize the state of the master thread.
*/ */
int i; int i, j;
assert(bdp != NULL); assert(bdp != NULL);
assert(bdp->bdr_thread != NULL); assert(bdp->bdr_device != NULL);
mthread_init(); mthread_init();
bdtab = bdp; bdtab = bdp;
for (i = 0; i < BLOCKDRIVER_MT_MAX_WORKERS; i++) /* Initialize device-specific data structures. */
worker[i].state = STATE_DEAD; for (i = 0; i < MAX_DEVICES; i++) {
device[i].id = i;
device[i].workers = 1;
mthread_event_init(&device[i].queue_event);
mthread_rwlock_init(&device[i].barrier);
for (j = 0; j < MAX_WORKERS; j++)
device[i].worker[j].state = STATE_DEAD;
}
/* Initialize a per-thread key, where each worker thread stores its own /* Initialize a per-thread key, where each worker thread stores its own
* reference to the worker structure. * reference to the worker structure.
@ -279,6 +358,23 @@ PRIVATE void master_init(struct blockdriver *bdp)
panic("blockdriver_mt: error initializing worker key"); panic("blockdriver_mt: error initializing worker key");
} }
/*===========================================================================*
* blockdriver_mt_get_tid *
*===========================================================================*/
PUBLIC thread_id_t blockdriver_mt_get_tid(void)
{
/* Return back the ID of this thread.
*/
worker_t *wp;
wp = (worker_t *) mthread_getspecific(worker_key);
if (wp == NULL)
panic("blockdriver_mt: master thread cannot query thread ID\n");
return MAKE_TID(wp->device_id, wp->worker_id);
}
/*===========================================================================* /*===========================================================================*
* blockdriver_mt_receive * * blockdriver_mt_receive *
*===========================================================================*/ *===========================================================================*/
@ -301,7 +397,7 @@ PUBLIC void blockdriver_mt_task(struct blockdriver *driver_tab)
{ {
/* The multithreaded driver task. /* The multithreaded driver task.
*/ */
int ipc_status; int ipc_status, i;
message mess; message mess;
/* Initialize first if necessary. */ /* Initialize first if necessary. */
@ -329,6 +425,10 @@ PUBLIC void blockdriver_mt_task(struct blockdriver *driver_tab)
if (num_exited > 0) if (num_exited > 0)
master_handle_exits(); master_handle_exits();
} }
/* Free up resources. */
for (i = 0; i < MAX_DEVICES; i++)
mthread_event_destroy(&device[i].queue_event);
} }
/*===========================================================================* /*===========================================================================*
@ -356,7 +456,7 @@ PUBLIC void blockdriver_mt_sleep(void)
if (wp == NULL) if (wp == NULL)
panic("blockdriver_mt: master thread cannot sleep"); panic("blockdriver_mt: master thread cannot sleep");
event_wait(&wp->sleep_event); mthread_event_wait(&wp->sleep_event);
} }
/*===========================================================================* /*===========================================================================*
@ -367,32 +467,41 @@ PUBLIC void blockdriver_mt_wakeup(thread_id_t id)
/* Wake up a sleeping worker thread from the master thread. /* Wake up a sleeping worker thread from the master thread.
*/ */
worker_t *wp; worker_t *wp;
device_id_t device_id;
worker_id_t worker_id;
assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS); device_id = TID_DEVICE(id);
worker_id = TID_WORKER(id);
wp = &worker[id]; assert(device_id >= 0 && device_id < MAX_DEVICES);
assert(worker_id >= 0 && worker_id < MAX_WORKERS);
assert(wp->state == STATE_RUNNING || wp->state == STATE_STOPPING); wp = &device[device_id].worker[worker_id];
event_fire(&wp->sleep_event); assert(wp->state == STATE_RUNNING || wp->state == STATE_BUSY);
mthread_event_fire(&wp->sleep_event);
} }
/*===========================================================================* /*===========================================================================*
* blockdriver_mt_stop * * blockdriver_mt_set_workers *
*===========================================================================*/ *===========================================================================*/
PUBLIC void blockdriver_mt_stop(void) PUBLIC void blockdriver_mt_set_workers(device_id_t id, int workers)
{ {
/* Put up the current worker thread for termination. Once the current dispatch /* Set the number of worker threads for the given device.
* call has finished, and there are no more messages in the thread's message
* queue, the thread will be terminated. Any messages in the queue will undo
* the effect of this call.
*/ */
worker_t *wp; device_t *dp;
wp = (worker_t *) mthread_getspecific(worker_key); assert(id >= 0 && id < MAX_DEVICES);
assert(wp != NULL); if (workers > MAX_WORKERS)
assert(wp->state == STATE_RUNNING || wp->state == STATE_STOPPING); workers = MAX_WORKERS;
wp->state = STATE_STOPPING; dp = &device[id];
/* If we are cleaning up, wake up all threads waiting on a queue event. */
if (workers == 1 && dp->workers > workers)
mthread_event_fire_all(&dp->queue_event);
dp->workers = workers;
} }

View file

@ -14,6 +14,7 @@
#include <minix/drivers.h> #include <minix/drivers.h>
#include <minix/blockdriver.h> #include <minix/blockdriver.h>
#include "const.h"
#include "driver.h" #include "driver.h"
#include "mq.h" #include "mq.h"

View file

@ -1,71 +0,0 @@
/* This file contains a simple thread event implementation.
*/
#include <minix/mthread.h>
#include <minix/sysutil.h>
#include "event.h"
/*===========================================================================*
* event_init *
*===========================================================================*/
PUBLIC void event_init(event_t *event)
{
/* Initialize an event object.
*/
int r;
if ((r = mthread_mutex_init(&event->mutex, NULL)) != 0)
panic("libblockdriver: error initializing mutex (%d)", r);
if ((r = mthread_cond_init(&event->cond, NULL)) != 0)
panic("libblockdriver: error initializing condvar (%d)", r);
}
/*===========================================================================*
* event_destroy *
*===========================================================================*/
PUBLIC void event_destroy(event_t *event)
{
/* Destroy an event object.
*/
int r;
if ((r = mthread_cond_destroy(&event->cond)) != 0)
panic("libblockdriver: error destroying condvar (%d)", r);
if ((r = mthread_mutex_destroy(&event->mutex)) != 0)
panic("libblockdriver: error destroying mutex (%d)", r);
}
/*===========================================================================*
* event_wait *
*===========================================================================*/
PUBLIC void event_wait(event_t *event)
{
/* Wait for an event, blocking the current thread in the process.
*/
int r;
if ((r = mthread_mutex_lock(&event->mutex)) != 0)
panic("libblockdriver: error locking mutex (%d)", r);
if ((r = mthread_cond_wait(&event->cond, &event->mutex)) != 0)
panic("libblockdriver: error waiting for condvar (%d)", r);
if ((r = mthread_mutex_unlock(&event->mutex)) != 0)
panic("libblockdriver: error unlocking mutex (%d)", r);
}
/*===========================================================================*
* event_fire *
*===========================================================================*/
PUBLIC void event_fire(event_t *event)
{
/* Fire an event, waking up any thread blocked on it without scheduling it.
*/
int r;
if ((r = mthread_mutex_lock(&event->mutex)) != 0)
panic("libblockdriver: error locking mutex (%d)", r);
if ((r = mthread_cond_signal(&event->cond)) != 0)
panic("libblockdriver: error signaling condvar (%d)", r);
if ((r = mthread_mutex_unlock(&event->mutex)) != 0)
panic("libblockdriver: error unlocking mutex (%d)", r);
}

View file

@ -1,14 +0,0 @@
#ifndef _BLOCKDRIVER_EVENT_H
#define _BLOCKDRIVER_EVENT_H
typedef struct {
mthread_mutex_t mutex;
mthread_cond_t cond;
} event_t;
_PROTOTYPE( void event_init, (event_t *event) );
_PROTOTYPE( void event_destroy, (event_t *event) );
_PROTOTYPE( void event_wait, (event_t *event) );
_PROTOTYPE( void event_fire, (event_t *event) );
#endif /* _BLOCKDRIVER_EVENT_H */

View file

@ -10,6 +10,7 @@
#include <sys/queue.h> #include <sys/queue.h>
#include <assert.h> #include <assert.h>
#include "const.h"
#include "mq.h" #include "mq.h"
#define MQ_SIZE 128 #define MQ_SIZE 128
@ -21,8 +22,7 @@ struct mq_cell {
}; };
PRIVATE struct mq_cell pool[MQ_SIZE]; PRIVATE struct mq_cell pool[MQ_SIZE];
PRIVATE STAILQ_HEAD(queue, mq_cell) queue[MAX_DEVICES];
PRIVATE STAILQ_HEAD(queue, mq_cell) queue[BLOCKDRIVER_MT_MAX_WORKERS];
PRIVATE STAILQ_HEAD(free_list, mq_cell) free_list; PRIVATE STAILQ_HEAD(free_list, mq_cell) free_list;
/*===========================================================================* /*===========================================================================*
@ -36,7 +36,7 @@ PUBLIC void mq_init(void)
STAILQ_INIT(&free_list); STAILQ_INIT(&free_list);
for (i = 0; i < BLOCKDRIVER_MT_MAX_WORKERS; i++) for (i = 0; i < MAX_DEVICES; i++)
STAILQ_INIT(&queue[i]); STAILQ_INIT(&queue[i]);
for (i = 0; i < MQ_SIZE; i++) for (i = 0; i < MQ_SIZE; i++)
@ -46,15 +46,15 @@ PUBLIC void mq_init(void)
/*===========================================================================* /*===========================================================================*
* mq_enqueue * * mq_enqueue *
*===========================================================================*/ *===========================================================================*/
PUBLIC int mq_enqueue(thread_id_t thread_id, const message *mess, PUBLIC int mq_enqueue(device_id_t device_id, const message *mess,
int ipc_status) int ipc_status)
{ {
/* Add a message, including its IPC status, to the message queue of a thread. /* Add a message, including its IPC status, to the message queue of a device.
* Return TRUE iff the message was added successfully. * Return TRUE iff the message was added successfully.
*/ */
struct mq_cell *cell; struct mq_cell *cell;
assert(thread_id >= 0 && thread_id < BLOCKDRIVER_MT_MAX_WORKERS); assert(device_id >= 0 && device_id < MAX_DEVICES);
if (STAILQ_EMPTY(&free_list)) if (STAILQ_EMPTY(&free_list))
return FALSE; return FALSE;
@ -65,7 +65,7 @@ PUBLIC int mq_enqueue(thread_id_t thread_id, const message *mess,
cell->mess = *mess; cell->mess = *mess;
cell->ipc_status = ipc_status; cell->ipc_status = ipc_status;
STAILQ_INSERT_TAIL(&queue[thread_id], cell, next); STAILQ_INSERT_TAIL(&queue[device_id], cell, next);
return TRUE; return TRUE;
} }
@ -73,20 +73,20 @@ PUBLIC int mq_enqueue(thread_id_t thread_id, const message *mess,
/*===========================================================================* /*===========================================================================*
* mq_dequeue * * mq_dequeue *
*===========================================================================*/ *===========================================================================*/
PUBLIC int mq_dequeue(thread_id_t thread_id, message *mess, int *ipc_status) PUBLIC int mq_dequeue(device_id_t device_id, message *mess, int *ipc_status)
{ {
/* Return and remove a message, including its IPC status, from the message /* Return and remove a message, including its IPC status, from the message
* queue of a thread. Return TRUE iff a message was available. * queue of a thread. Return TRUE iff a message was available.
*/ */
struct mq_cell *cell; struct mq_cell *cell;
assert(thread_id >= 0 && thread_id < BLOCKDRIVER_MT_MAX_WORKERS); assert(device_id >= 0 && device_id < MAX_DEVICES);
if (STAILQ_EMPTY(&queue[thread_id])) if (STAILQ_EMPTY(&queue[device_id]))
return FALSE; return FALSE;
cell = STAILQ_FIRST(&queue[thread_id]); cell = STAILQ_FIRST(&queue[device_id]);
STAILQ_REMOVE_HEAD(&queue[thread_id], next); STAILQ_REMOVE_HEAD(&queue[device_id], next);
*mess = cell->mess; *mess = cell->mess;
*ipc_status = cell->ipc_status; *ipc_status = cell->ipc_status;

View file

@ -2,9 +2,9 @@
#define _BLOCKDRIVER_MQ_H #define _BLOCKDRIVER_MQ_H
_PROTOTYPE( void mq_init, (void) ); _PROTOTYPE( void mq_init, (void) );
_PROTOTYPE( int mq_enqueue, (thread_id_t thread_id, const message *mess, _PROTOTYPE( int mq_enqueue, (device_id_t device_id, const message *mess,
int ipc_status) ); int ipc_status) );
_PROTOTYPE( int mq_dequeue, (thread_id_t thread_id, message *mess, _PROTOTYPE( int mq_dequeue, (device_id_t device_id, message *mess,
int *ipc_status) ); int *ipc_status) );
#endif /* _BLOCKDRIVER_MQ_H */ #endif /* _BLOCKDRIVER_MQ_H */

View file

@ -7,6 +7,7 @@
#include <minix/minlib.h> #include <minix/minlib.h>
#include <assert.h> #include <assert.h>
#include "const.h"
#include "trace.h" #include "trace.h"
#define NO_TRACEDEV ((dev_t) -1) #define NO_TRACEDEV ((dev_t) -1)
@ -24,7 +25,7 @@ PRIVATE u64_t trace_tsc;
* plus one for the main thread). Each pointer is set to NULL whenever no * plus one for the main thread). Each pointer is set to NULL whenever no
* operation is currently being traced for that thread, for whatever reason. * operation is currently being traced for that thread, for whatever reason.
*/ */
PRIVATE btrace_entry *trace_ptr[BLOCKDRIVER_MT_MAX_WORKERS + 1] = { NULL }; PRIVATE btrace_entry *trace_ptr[MAX_THREADS + 1] = { NULL };
/*===========================================================================* /*===========================================================================*
* trace_gettime * * trace_gettime *
@ -175,7 +176,7 @@ PUBLIC void trace_start(thread_id_t id, message *m_ptr)
if (!trace_enabled || trace_dev != m_ptr->BDEV_MINOR) return; if (!trace_enabled || trace_dev != m_ptr->BDEV_MINOR) return;
assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS + 1); assert(id >= 0 && id < MAX_THREADS + 1);
if (trace_pos == trace_size) if (trace_pos == trace_size)
return; return;
@ -254,7 +255,7 @@ PUBLIC void trace_setsize(thread_id_t id, size_t size)
if (!trace_enabled) return; if (!trace_enabled) return;
assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS + 1); assert(id >= 0 && id < MAX_THREADS + 1);
if ((entry = trace_ptr[id]) == NULL) return; if ((entry = trace_ptr[id]) == NULL) return;
@ -272,7 +273,7 @@ PUBLIC void trace_finish(thread_id_t id, int result)
if (!trace_enabled) return; if (!trace_enabled) return;
assert(id >= 0 && id < BLOCKDRIVER_MT_MAX_WORKERS + 1); assert(id >= 0 && id < MAX_THREADS + 1);
if ((entry = trace_ptr[id]) == NULL) return; if ((entry = trace_ptr[id]) == NULL) return;