|
@@ -55,6 +55,21 @@ struct BlockJobTxn {
|
|
|
|
|
|
static QLIST_HEAD(, BlockJob) block_jobs = QLIST_HEAD_INITIALIZER(block_jobs);
|
|
static QLIST_HEAD(, BlockJob) block_jobs = QLIST_HEAD_INITIALIZER(block_jobs);
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * The block job API is composed of two categories of functions.
|
|
|
|
+ *
|
|
|
|
+ * The first includes functions used by the monitor. The monitor is
|
|
|
|
+ * peculiar in that it accesses the block job list with block_job_get, and
|
|
|
|
+ * therefore needs consistency across block_job_get and the actual operation
|
|
|
|
+ * (e.g. block_job_set_speed). The consistency is achieved with
|
|
|
|
+ * aio_context_acquire/release. These functions are declared in blockjob.h.
|
|
|
|
+ *
|
|
|
|
+ * The second includes functions used by the block job drivers and sometimes
|
|
|
|
+ * by the core block layer. These do not care about locking, because the
|
|
|
|
+ * whole coroutine runs under the AioContext lock, and are declared in
|
|
|
|
+ * blockjob_int.h.
|
|
|
|
+ */
|
|
|
|
+
|
|
BlockJob *block_job_next(BlockJob *job)
|
|
BlockJob *block_job_next(BlockJob *job)
|
|
{
|
|
{
|
|
if (!job) {
|
|
if (!job) {
|
|
@@ -216,90 +231,6 @@ int block_job_add_bdrv(BlockJob *job, const char *name, BlockDriverState *bs,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-void *block_job_create(const char *job_id, const BlockJobDriver *driver,
|
|
|
|
- BlockDriverState *bs, uint64_t perm,
|
|
|
|
- uint64_t shared_perm, int64_t speed, int flags,
|
|
|
|
- BlockCompletionFunc *cb, void *opaque, Error **errp)
|
|
|
|
-{
|
|
|
|
- BlockBackend *blk;
|
|
|
|
- BlockJob *job;
|
|
|
|
- int ret;
|
|
|
|
-
|
|
|
|
- if (bs->job) {
|
|
|
|
- error_setg(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs));
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (job_id == NULL && !(flags & BLOCK_JOB_INTERNAL)) {
|
|
|
|
- job_id = bdrv_get_device_name(bs);
|
|
|
|
- if (!*job_id) {
|
|
|
|
- error_setg(errp, "An explicit job ID is required for this node");
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (job_id) {
|
|
|
|
- if (flags & BLOCK_JOB_INTERNAL) {
|
|
|
|
- error_setg(errp, "Cannot specify job ID for internal block job");
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!id_wellformed(job_id)) {
|
|
|
|
- error_setg(errp, "Invalid job ID '%s'", job_id);
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (block_job_get(job_id)) {
|
|
|
|
- error_setg(errp, "Job ID '%s' already in use", job_id);
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- blk = blk_new(perm, shared_perm);
|
|
|
|
- ret = blk_insert_bs(blk, bs, errp);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- blk_unref(blk);
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- job = g_malloc0(driver->instance_size);
|
|
|
|
- job->driver = driver;
|
|
|
|
- job->id = g_strdup(job_id);
|
|
|
|
- job->blk = blk;
|
|
|
|
- job->cb = cb;
|
|
|
|
- job->opaque = opaque;
|
|
|
|
- job->busy = false;
|
|
|
|
- job->paused = true;
|
|
|
|
- job->pause_count = 1;
|
|
|
|
- job->refcnt = 1;
|
|
|
|
-
|
|
|
|
- error_setg(&job->blocker, "block device is in use by block job: %s",
|
|
|
|
- BlockJobType_lookup[driver->job_type]);
|
|
|
|
- block_job_add_bdrv(job, "main node", bs, 0, BLK_PERM_ALL, &error_abort);
|
|
|
|
- bs->job = job;
|
|
|
|
-
|
|
|
|
- blk_set_dev_ops(blk, &block_job_dev_ops, job);
|
|
|
|
- bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker);
|
|
|
|
-
|
|
|
|
- QLIST_INSERT_HEAD(&block_jobs, job, job_list);
|
|
|
|
-
|
|
|
|
- blk_add_aio_context_notifier(blk, block_job_attached_aio_context,
|
|
|
|
- block_job_detach_aio_context, job);
|
|
|
|
-
|
|
|
|
- /* Only set speed when necessary to avoid NotSupported error */
|
|
|
|
- if (speed != 0) {
|
|
|
|
- Error *local_err = NULL;
|
|
|
|
-
|
|
|
|
- block_job_set_speed(job, speed, &local_err);
|
|
|
|
- if (local_err) {
|
|
|
|
- block_job_unref(job);
|
|
|
|
- error_propagate(errp, local_err);
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return job;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
bool block_job_is_internal(BlockJob *job)
|
|
bool block_job_is_internal(BlockJob *job)
|
|
{
|
|
{
|
|
return (job->id == NULL);
|
|
return (job->id == NULL);
|
|
@@ -334,11 +265,6 @@ void block_job_start(BlockJob *job)
|
|
bdrv_coroutine_enter(blk_bs(job->blk), job->co);
|
|
bdrv_coroutine_enter(blk_bs(job->blk), job->co);
|
|
}
|
|
}
|
|
|
|
|
|
-void block_job_early_fail(BlockJob *job)
|
|
|
|
-{
|
|
|
|
- block_job_unref(job);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
static void block_job_completed_single(BlockJob *job)
|
|
static void block_job_completed_single(BlockJob *job)
|
|
{
|
|
{
|
|
if (!job->ret) {
|
|
if (!job->ret) {
|
|
@@ -440,21 +366,6 @@ static void block_job_completed_txn_success(BlockJob *job)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-void block_job_completed(BlockJob *job, int ret)
|
|
|
|
-{
|
|
|
|
- assert(blk_bs(job->blk)->job == job);
|
|
|
|
- assert(!job->completed);
|
|
|
|
- job->completed = true;
|
|
|
|
- job->ret = ret;
|
|
|
|
- if (!job->txn) {
|
|
|
|
- block_job_completed_single(job);
|
|
|
|
- } else if (ret < 0 || block_job_is_cancelled(job)) {
|
|
|
|
- block_job_completed_txn_abort(job);
|
|
|
|
- } else {
|
|
|
|
- block_job_completed_txn_success(job);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|
{
|
|
{
|
|
Error *local_err = NULL;
|
|
Error *local_err = NULL;
|
|
@@ -492,44 +403,11 @@ void block_job_user_pause(BlockJob *job)
|
|
block_job_pause(job);
|
|
block_job_pause(job);
|
|
}
|
|
}
|
|
|
|
|
|
-static bool block_job_should_pause(BlockJob *job)
|
|
|
|
-{
|
|
|
|
- return job->pause_count > 0;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
bool block_job_user_paused(BlockJob *job)
|
|
bool block_job_user_paused(BlockJob *job)
|
|
{
|
|
{
|
|
return job->user_paused;
|
|
return job->user_paused;
|
|
}
|
|
}
|
|
|
|
|
|
-void coroutine_fn block_job_pause_point(BlockJob *job)
|
|
|
|
-{
|
|
|
|
- assert(job && block_job_started(job));
|
|
|
|
-
|
|
|
|
- if (!block_job_should_pause(job)) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (block_job_is_cancelled(job)) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (job->driver->pause) {
|
|
|
|
- job->driver->pause(job);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
|
|
|
|
- job->paused = true;
|
|
|
|
- job->busy = false;
|
|
|
|
- qemu_coroutine_yield(); /* wait for block_job_resume() */
|
|
|
|
- job->busy = true;
|
|
|
|
- job->paused = false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (job->driver->resume) {
|
|
|
|
- job->driver->resume(job);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void block_job_user_resume(BlockJob *job)
|
|
void block_job_user_resume(BlockJob *job)
|
|
{
|
|
{
|
|
if (job && job->user_paused && job->pause_count > 0) {
|
|
if (job && job->user_paused && job->pause_count > 0) {
|
|
@@ -538,13 +416,6 @@ void block_job_user_resume(BlockJob *job)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-void block_job_enter(BlockJob *job)
|
|
|
|
-{
|
|
|
|
- if (job->co && !job->busy) {
|
|
|
|
- bdrv_coroutine_enter(blk_bs(job->blk), job->co);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void block_job_cancel(BlockJob *job)
|
|
void block_job_cancel(BlockJob *job)
|
|
{
|
|
{
|
|
if (block_job_started(job)) {
|
|
if (block_job_started(job)) {
|
|
@@ -556,11 +427,6 @@ void block_job_cancel(BlockJob *job)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-bool block_job_is_cancelled(BlockJob *job)
|
|
|
|
-{
|
|
|
|
- return job->cancelled;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void block_job_iostatus_reset(BlockJob *job)
|
|
void block_job_iostatus_reset(BlockJob *job)
|
|
{
|
|
{
|
|
job->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
|
|
job->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
|
|
@@ -628,42 +494,6 @@ int block_job_complete_sync(BlockJob *job, Error **errp)
|
|
return block_job_finish_sync(job, &block_job_complete, errp);
|
|
return block_job_finish_sync(job, &block_job_complete, errp);
|
|
}
|
|
}
|
|
|
|
|
|
-void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns)
|
|
|
|
-{
|
|
|
|
- assert(job->busy);
|
|
|
|
-
|
|
|
|
- /* Check cancellation *before* setting busy = false, too! */
|
|
|
|
- if (block_job_is_cancelled(job)) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- job->busy = false;
|
|
|
|
- if (!block_job_should_pause(job)) {
|
|
|
|
- co_aio_sleep_ns(blk_get_aio_context(job->blk), type, ns);
|
|
|
|
- }
|
|
|
|
- job->busy = true;
|
|
|
|
-
|
|
|
|
- block_job_pause_point(job);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void block_job_yield(BlockJob *job)
|
|
|
|
-{
|
|
|
|
- assert(job->busy);
|
|
|
|
-
|
|
|
|
- /* Check cancellation *before* setting busy = false, too! */
|
|
|
|
- if (block_job_is_cancelled(job)) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- job->busy = false;
|
|
|
|
- if (!block_job_should_pause(job)) {
|
|
|
|
- qemu_coroutine_yield();
|
|
|
|
- }
|
|
|
|
- job->busy = true;
|
|
|
|
-
|
|
|
|
- block_job_pause_point(job);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
|
|
BlockJobInfo *block_job_query(BlockJob *job, Error **errp)
|
|
{
|
|
{
|
|
BlockJobInfo *info;
|
|
BlockJobInfo *info;
|
|
@@ -723,6 +553,95 @@ static void block_job_event_completed(BlockJob *job, const char *msg)
|
|
&error_abort);
|
|
&error_abort);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * API for block job drivers and the block layer. These functions are
|
|
|
|
+ * declared in blockjob_int.h.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+void *block_job_create(const char *job_id, const BlockJobDriver *driver,
|
|
|
|
+ BlockDriverState *bs, uint64_t perm,
|
|
|
|
+ uint64_t shared_perm, int64_t speed, int flags,
|
|
|
|
+ BlockCompletionFunc *cb, void *opaque, Error **errp)
|
|
|
|
+{
|
|
|
|
+ BlockBackend *blk;
|
|
|
|
+ BlockJob *job;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ if (bs->job) {
|
|
|
|
+ error_setg(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs));
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (job_id == NULL && !(flags & BLOCK_JOB_INTERNAL)) {
|
|
|
|
+ job_id = bdrv_get_device_name(bs);
|
|
|
|
+ if (!*job_id) {
|
|
|
|
+ error_setg(errp, "An explicit job ID is required for this node");
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (job_id) {
|
|
|
|
+ if (flags & BLOCK_JOB_INTERNAL) {
|
|
|
|
+ error_setg(errp, "Cannot specify job ID for internal block job");
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!id_wellformed(job_id)) {
|
|
|
|
+ error_setg(errp, "Invalid job ID '%s'", job_id);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (block_job_get(job_id)) {
|
|
|
|
+ error_setg(errp, "Job ID '%s' already in use", job_id);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ blk = blk_new(perm, shared_perm);
|
|
|
|
+ ret = blk_insert_bs(blk, bs, errp);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ blk_unref(blk);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ job = g_malloc0(driver->instance_size);
|
|
|
|
+ job->driver = driver;
|
|
|
|
+ job->id = g_strdup(job_id);
|
|
|
|
+ job->blk = blk;
|
|
|
|
+ job->cb = cb;
|
|
|
|
+ job->opaque = opaque;
|
|
|
|
+ job->busy = false;
|
|
|
|
+ job->paused = true;
|
|
|
|
+ job->pause_count = 1;
|
|
|
|
+ job->refcnt = 1;
|
|
|
|
+
|
|
|
|
+ error_setg(&job->blocker, "block device is in use by block job: %s",
|
|
|
|
+ BlockJobType_lookup[driver->job_type]);
|
|
|
|
+ block_job_add_bdrv(job, "main node", bs, 0, BLK_PERM_ALL, &error_abort);
|
|
|
|
+ bs->job = job;
|
|
|
|
+
|
|
|
|
+ blk_set_dev_ops(blk, &block_job_dev_ops, job);
|
|
|
|
+ bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker);
|
|
|
|
+
|
|
|
|
+ QLIST_INSERT_HEAD(&block_jobs, job, job_list);
|
|
|
|
+
|
|
|
|
+ blk_add_aio_context_notifier(blk, block_job_attached_aio_context,
|
|
|
|
+ block_job_detach_aio_context, job);
|
|
|
|
+
|
|
|
|
+ /* Only set speed when necessary to avoid NotSupported error */
|
|
|
|
+ if (speed != 0) {
|
|
|
|
+ Error *local_err = NULL;
|
|
|
|
+
|
|
|
|
+ block_job_set_speed(job, speed, &local_err);
|
|
|
|
+ if (local_err) {
|
|
|
|
+ block_job_unref(job);
|
|
|
|
+ error_propagate(errp, local_err);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return job;
|
|
|
|
+}
|
|
|
|
+
|
|
void block_job_pause_all(void)
|
|
void block_job_pause_all(void)
|
|
{
|
|
{
|
|
BlockJob *job = NULL;
|
|
BlockJob *job = NULL;
|
|
@@ -735,6 +654,59 @@ void block_job_pause_all(void)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+void block_job_early_fail(BlockJob *job)
|
|
|
|
+{
|
|
|
|
+ block_job_unref(job);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void block_job_completed(BlockJob *job, int ret)
|
|
|
|
+{
|
|
|
|
+ assert(blk_bs(job->blk)->job == job);
|
|
|
|
+ assert(!job->completed);
|
|
|
|
+ job->completed = true;
|
|
|
|
+ job->ret = ret;
|
|
|
|
+ if (!job->txn) {
|
|
|
|
+ block_job_completed_single(job);
|
|
|
|
+ } else if (ret < 0 || block_job_is_cancelled(job)) {
|
|
|
|
+ block_job_completed_txn_abort(job);
|
|
|
|
+ } else {
|
|
|
|
+ block_job_completed_txn_success(job);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool block_job_should_pause(BlockJob *job)
|
|
|
|
+{
|
|
|
|
+ return job->pause_count > 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void coroutine_fn block_job_pause_point(BlockJob *job)
|
|
|
|
+{
|
|
|
|
+ assert(job && block_job_started(job));
|
|
|
|
+
|
|
|
|
+ if (!block_job_should_pause(job)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (block_job_is_cancelled(job)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (job->driver->pause) {
|
|
|
|
+ job->driver->pause(job);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (block_job_should_pause(job) && !block_job_is_cancelled(job)) {
|
|
|
|
+ job->paused = true;
|
|
|
|
+ job->busy = false;
|
|
|
|
+ qemu_coroutine_yield(); /* wait for block_job_resume() */
|
|
|
|
+ job->busy = true;
|
|
|
|
+ job->paused = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (job->driver->resume) {
|
|
|
|
+ job->driver->resume(job);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
void block_job_resume_all(void)
|
|
void block_job_resume_all(void)
|
|
{
|
|
{
|
|
BlockJob *job = NULL;
|
|
BlockJob *job = NULL;
|
|
@@ -747,6 +719,54 @@ void block_job_resume_all(void)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+void block_job_enter(BlockJob *job)
|
|
|
|
+{
|
|
|
|
+ if (job->co && !job->busy) {
|
|
|
|
+ bdrv_coroutine_enter(blk_bs(job->blk), job->co);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool block_job_is_cancelled(BlockJob *job)
|
|
|
|
+{
|
|
|
|
+ return job->cancelled;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns)
|
|
|
|
+{
|
|
|
|
+ assert(job->busy);
|
|
|
|
+
|
|
|
|
+ /* Check cancellation *before* setting busy = false, too! */
|
|
|
|
+ if (block_job_is_cancelled(job)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ job->busy = false;
|
|
|
|
+ if (!block_job_should_pause(job)) {
|
|
|
|
+ co_aio_sleep_ns(blk_get_aio_context(job->blk), type, ns);
|
|
|
|
+ }
|
|
|
|
+ job->busy = true;
|
|
|
|
+
|
|
|
|
+ block_job_pause_point(job);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void block_job_yield(BlockJob *job)
|
|
|
|
+{
|
|
|
|
+ assert(job->busy);
|
|
|
|
+
|
|
|
|
+ /* Check cancellation *before* setting busy = false, too! */
|
|
|
|
+ if (block_job_is_cancelled(job)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ job->busy = false;
|
|
|
|
+ if (!block_job_should_pause(job)) {
|
|
|
|
+ qemu_coroutine_yield();
|
|
|
|
+ }
|
|
|
|
+ job->busy = true;
|
|
|
|
+
|
|
|
|
+ block_job_pause_point(job);
|
|
|
|
+}
|
|
|
|
+
|
|
void block_job_event_ready(BlockJob *job)
|
|
void block_job_event_ready(BlockJob *job)
|
|
{
|
|
{
|
|
job->ready = true;
|
|
job->ready = true;
|