2155 lines
66 KiB
Diff
2155 lines
66 KiB
Diff
diff --git a/block/Kconfig.iosched b/block/Kconfig.iosched
|
|
index 27f11320b8d1..e98585dd83e0 100644
|
|
--- a/block/Kconfig.iosched
|
|
+++ b/block/Kconfig.iosched
|
|
@@ -16,6 +16,20 @@ config MQ_IOSCHED_KYBER
|
|
synchronous writes, it will self-tune queue depths to achieve that
|
|
goal.
|
|
|
|
+config MQ_IOSCHED_ADIOS
|
|
+ tristate "Adaptive Deadline I/O scheduler"
|
|
+ default m
|
|
+ help
|
|
+ The Adaptive Deadline I/O Scheduler (ADIOS) is a multi-queue I/O
|
|
+ scheduler with learning-based adaptive latency control.
|
|
+
|
|
+config MQ_IOSCHED_DEFAULT_ADIOS
|
|
+ bool "Enable ADIOS I/O scheduler as default MQ I/O scheduler"
|
|
+ depends on MQ_IOSCHED_ADIOS=y
|
|
+ default n
|
|
+ help
|
|
+ Enable the ADIOS I/O scheduler as the default scheduler for MQ I/O.
|
|
+
|
|
config IOSCHED_BFQ
|
|
tristate "BFQ I/O scheduler"
|
|
select BLK_ICQ
|
|
diff --git a/block/Makefile b/block/Makefile
|
|
index e7bd320e3d69..5523e5f62f92 100644
|
|
--- a/block/Makefile
|
|
+++ b/block/Makefile
|
|
@@ -25,6 +25,7 @@ obj-$(CONFIG_BLK_CGROUP_IOLATENCY) += blk-iolatency.o
|
|
obj-$(CONFIG_BLK_CGROUP_IOCOST) += blk-iocost.o
|
|
obj-$(CONFIG_MQ_IOSCHED_DEADLINE) += mq-deadline.o
|
|
obj-$(CONFIG_MQ_IOSCHED_KYBER) += kyber-iosched.o
|
|
+obj-$(CONFIG_MQ_IOSCHED_ADIOS) += adios.o
|
|
bfq-y := bfq-iosched.o bfq-wf2q.o bfq-cgroup.o
|
|
obj-$(CONFIG_IOSCHED_BFQ) += bfq.o
|
|
|
|
@@ -39,3 +40,10 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \
|
|
blk-crypto-sysfs.o
|
|
obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o
|
|
obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o
|
|
+
|
|
+all:
|
|
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
|
+
|
|
+clean:
|
|
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
|
+
|
|
diff --git a/block/adios.c b/block/adios.c
|
|
new file mode 100644
|
|
index 000000000000..c7f380bb65dd
|
|
--- /dev/null
|
|
+++ b/block/adios.c
|
|
@@ -0,0 +1,2062 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Adaptive Deadline I/O Scheduler (ADIOS)
|
|
+ * Copyright (C) 2025 Masahito Suzuki
|
|
+ */
|
|
+#include <linux/bio.h>
|
|
+#include <linux/blkdev.h>
|
|
+#include <linux/compiler.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/math.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/rbtree.h>
|
|
+#include <linux/sbitmap.h>
|
|
+#include <linux/mempool.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/timekeeping.h>
|
|
+#include <linux/percpu.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/list_sort.h>
|
|
+#include <linux/rcupdate.h>
|
|
+
|
|
+#include "elevator.h"
|
|
+#include "blk.h"
|
|
+#include "blk-mq.h"
|
|
+#include "blk-mq-sched.h"
|
|
+
|
|
+#define ADIOS_VERSION "3.2.0"
|
|
+
|
|
+/* Request Types:
|
|
+ *
|
|
+ * Tier 0 (Highest Priority): Emergency & System Integrity Requests
|
|
+ * -----------------------------------------------------------------
|
|
+ * - Target: Requests with the BLK_MQ_INSERT_AT_HEAD flag.
|
|
+ * - Purpose: For critical, non-negotiable operations such as device error
|
|
+ * recovery or flush sequences that must bypass all other scheduling logic.
|
|
+ * - Implementation: Placed in a dedicated, high-priority FIFO queue
|
|
+ * (`prio_queue[0]`) for immediate dispatch.
|
|
+ *
|
|
+ * Tier 1 (High Priority): I/O Barrier Guarantees
|
|
+ * ---------------------------------------------------------------
|
|
+ * - Target: Requests with the REQ_OP_FLUSH flag.
|
|
+ * - Purpose: To enforce a strict I/O barrier. When a flush request is
|
|
+ * received, the scheduler stops processing new requests from its main
|
|
+ * queues until all preceding requests have been completed. This guarantees
|
|
+ * the order of operations required by filesystems for data integrity.
|
|
+ * - Implementation: A state flag (ADIOS_STATE_BARRIER) halts
|
|
+ * insertion into the main deadline tree. The barrier request and all
|
|
+ * subsequent requests are held in a temporary `barrier_queue`. Once the
|
|
+ * main queues are drained, the barrier request and the subsequent requests
|
|
+ * are released from the pending queue back into the scheduler.
|
|
+ *
|
|
+ * Tier 2 (Medium Priority): Application Responsiveness
|
|
+ * ----------------------------------------------------
|
|
+ * - Target: Normal synchronous requests (e.g., from standard file reads).
|
|
+ * - Purpose: To ensure correct application behavior for operations that
|
|
+ * depend on sequential I/O completion (e.g., file system mounts) and to
|
|
+ * provide low latency for interactive applications.
|
|
+ * - Implementation: The deadline for these requests is set to their start
|
|
+ * time (`rq->start_time_ns`). This effectively enforces FIFO-like behavior
|
|
+ * within the deadline-sorted red-black tree, preventing out-of-order
|
|
+ * execution of dependent synchronous operations.
|
|
+ *
|
|
+ * Tier 3 (Normal Priority): Background Throughput
|
|
+ * -----------------------------------------------
|
|
+ * - Target: Asynchronous requests.
|
|
+ * - Purpose: To maximize disk throughput for background tasks where latency
|
|
+ * is not critical.
|
|
+ * - Implementation: These are the only requests where ADIOS's adaptive
|
|
+ * latency prediction model is used. A dynamic deadline is calculated based
|
|
+ * on the predicted I/O latency, allowing for aggressive reordering to
|
|
+ * optimize I/O efficiency.
|
|
+ *
|
|
+ * Dispatch Logic:
|
|
+ * The scheduler always dispatches requests in strict priority order:
|
|
+ * 1. prio_queue[0] (Tier 0)
|
|
+ * 2. The deadline-sorted batch queue (which naturally prioritizes Tier 2
|
|
+ * over Tier 3 due to their calculated deadlines).
|
|
+ * 3. Barrier-pending requests are handled only after the main queues are empty.
|
|
+ */
|
|
+
|
|
+// Global variable to control the latency
|
|
+static u64 default_global_latency_window = 16000000ULL;
|
|
+static u64 default_global_latency_window_rotational = 22000000ULL;
|
|
+// Ratio below which batch queues should be refilled
|
|
+static u8 default_bq_refill_below_ratio = 20;
|
|
+// Maximum latency sample to input
|
|
+static u64 default_lat_model_latency_limit = 500 * NSEC_PER_MSEC;
|
|
+// Batch ordering strategy
|
|
+static u64 default_batch_order = 0;
|
|
+
|
|
+/* Compliance Flags:
|
|
+ * 0x1: Async requests will not be reordered based on the predicted latency
|
|
+ */
|
|
+enum adios_compliance_flags {
|
|
+ ADIOS_CF_FIXORDER = 1U << 0,
|
|
+};
|
|
+
|
|
+// Flags to control compliance with block layer constraints
|
|
+static u64 default_compliance_flags = 0x0;
|
|
+
|
|
+// Dynamic thresholds for shrinkage
|
|
+static u32 default_lm_shrink_at_kreqs = 5000;
|
|
+static u32 default_lm_shrink_at_gbytes = 50;
|
|
+static u32 default_lm_shrink_resist = 2;
|
|
+
|
|
+enum adios_optype {
|
|
+ ADIOS_READ = 0,
|
|
+ ADIOS_WRITE = 1,
|
|
+ ADIOS_DISCARD = 2,
|
|
+ ADIOS_OTHER = 3,
|
|
+ ADIOS_OPTYPES = 4,
|
|
+};
|
|
+
|
|
+// Latency targets for each operation type
|
|
+static u64 default_latency_target[ADIOS_OPTYPES] = {
|
|
+ [ADIOS_READ] = 2ULL * NSEC_PER_MSEC,
|
|
+ [ADIOS_WRITE] = 2000ULL * NSEC_PER_MSEC,
|
|
+ [ADIOS_DISCARD] = 8000ULL * NSEC_PER_MSEC,
|
|
+ [ADIOS_OTHER] = 0ULL * NSEC_PER_MSEC,
|
|
+};
|
|
+
|
|
+// Maximum batch size limits for each operation type
|
|
+static u32 default_batch_limit[ADIOS_OPTYPES] = {
|
|
+ [ADIOS_READ] = 36,
|
|
+ [ADIOS_WRITE] = 72,
|
|
+ [ADIOS_DISCARD] = 1,
|
|
+ [ADIOS_OTHER] = 1,
|
|
+};
|
|
+
|
|
+enum adios_batch_order {
|
|
+ ADIOS_BO_OPTYPE = 0,
|
|
+ ADIOS_BO_ELEVATOR = 1,
|
|
+};
|
|
+
|
|
+// Thresholds for latency model control
|
|
+#define LM_BLOCK_SIZE_THRESHOLD 4096
|
|
+#define LM_SAMPLES_THRESHOLD 1024
|
|
+#define LM_INTERVAL_THRESHOLD 1500
|
|
+#define LM_OUTLIER_PERCENTILE 99
|
|
+#define LM_LAT_BUCKET_COUNT 64
|
|
+
|
|
+#define ADIOS_PQ_LEVELS 2
|
|
+#define ADIOS_DL_TYPES 2
|
|
+#define ADIOS_BQ_PAGES 2
|
|
+
|
|
+static u32 default_dl_prio[ADIOS_DL_TYPES] = {8, 0};
|
|
+
|
|
+// Bit flags for the atomic state variable, indicating which queues have requests.
|
|
+enum adios_state_flags {
|
|
+ ADIOS_STATE_PQ_0 = 1U << 0,
|
|
+ ADIOS_STATE_PQ_1 = 1U << 1,
|
|
+ ADIOS_STATE_DL_0 = 1U << 2,
|
|
+ ADIOS_STATE_DL_1 = 1U << 3,
|
|
+ ADIOS_STATE_BQ_PAGE_0 = 1U << 4,
|
|
+ ADIOS_STATE_BQ_PAGE_1 = 1U << 5,
|
|
+ ADIOS_STATE_BARRIER = 1U << 6,
|
|
+};
|
|
+#define ADIOS_STATE_PQ 0
|
|
+#define ADIOS_STATE_DL 2
|
|
+#define ADIOS_STATE_BQ 4
|
|
+#define ADIOS_STATE_BP 6
|
|
+
|
|
+// Temporal granularity of the deadline tree node (dl_group)
|
|
+#define ADIOS_QUANTUM_SHIFT 20
|
|
+
|
|
+#define ADIOS_MAX_INSERTS_PER_LOCK 72
|
|
+#define ADIOS_MAX_DELETES_PER_LOCK 24
|
|
+
|
|
+// Structure to hold latency bucket data for small requests
|
|
+struct latency_bucket_small {
|
|
+ u64 weighted_sum_latency;
|
|
+ u64 sum_of_weights;
|
|
+};
|
|
+
|
|
+// Structure to hold latency bucket data for large requests
|
|
+struct latency_bucket_large {
|
|
+ u64 weighted_sum_latency;
|
|
+ u64 weighted_sum_block_size;
|
|
+ u64 sum_of_weights;
|
|
+};
|
|
+
|
|
+// Structure to hold per-cpu buckets, improving data locality and code clarity.
|
|
+struct lm_buckets {
|
|
+ struct latency_bucket_small small_bucket[LM_LAT_BUCKET_COUNT];
|
|
+ struct latency_bucket_large large_bucket[LM_LAT_BUCKET_COUNT];
|
|
+};
|
|
+
|
|
+// Structure to hold RCU-protected latency model parameters
|
|
+struct latency_model_params {
|
|
+ u64 base;
|
|
+ u64 slope;
|
|
+ u64 small_sum_delay;
|
|
+ u64 small_count;
|
|
+ u64 large_sum_delay;
|
|
+ u64 large_sum_bsize;
|
|
+ u64 last_update_jiffies;
|
|
+ struct rcu_head rcu;
|
|
+};
|
|
+
|
|
+// Structure to hold the latency model context data
|
|
+struct latency_model {
|
|
+ spinlock_t update_lock;
|
|
+ struct latency_model_params __rcu *params;
|
|
+
|
|
+ // Per-CPU buckets to avoid lock contention on the completion path
|
|
+ struct lm_buckets __percpu *pcpu_buckets;
|
|
+ // Per-CPU snapshots for delta-based aggregation (accessed under update_lock)
|
|
+ struct lm_buckets __percpu *pcpu_snapshot;
|
|
+
|
|
+ u32 lm_shrink_at_kreqs;
|
|
+ u32 lm_shrink_at_gbytes;
|
|
+ u8 lm_shrink_resist;
|
|
+};
|
|
+
|
|
+struct adios_pcpu_completion {
|
|
+ u64 last_completed_time;
|
|
+ sector_t last_completed_pos;
|
|
+};
|
|
+
|
|
+union adios_in_flight_rqs {
|
|
+ atomic64_t atomic;
|
|
+ u64 scalar;
|
|
+ struct {
|
|
+ u64 count: 16;
|
|
+ u64 total_pred_lat: 48;
|
|
+ };
|
|
+};
|
|
+
|
|
+// Adios scheduler data
|
|
+struct adios_data {
|
|
+ spinlock_t pq_lock;
|
|
+ struct list_head prio_queue[2];
|
|
+
|
|
+ struct rb_root_cached dl_tree[2];
|
|
+ spinlock_t lock;
|
|
+ s64 dl_bias;
|
|
+ s32 dl_prio[2];
|
|
+
|
|
+ atomic_t state;
|
|
+ u8 bq_state[ADIOS_BQ_PAGES];
|
|
+
|
|
+ bool models_stable;
|
|
+
|
|
+ u64 global_latency_window;
|
|
+ u64 compliance_flags;
|
|
+ u64 latency_target[ADIOS_OPTYPES];
|
|
+ u32 batch_limit[ADIOS_OPTYPES];
|
|
+ u32 batch_actual_max_size[ADIOS_OPTYPES];
|
|
+ u32 batch_actual_max_total;
|
|
+ u32 async_depth;
|
|
+ u32 lat_model_latency_limit;
|
|
+ u8 bq_refill_below_ratio;
|
|
+ u8 is_rotational;
|
|
+ u8 batch_order;
|
|
+ u8 elv_direction;
|
|
+ sector_t head_pos;
|
|
+
|
|
+ bool bq_page;
|
|
+ struct list_head batch_queue[ADIOS_BQ_PAGES][ADIOS_OPTYPES];
|
|
+ u32 batch_count[ADIOS_BQ_PAGES][ADIOS_OPTYPES];
|
|
+ u8 bq_batch_order[ADIOS_BQ_PAGES];
|
|
+ spinlock_t bq_lock;
|
|
+ spinlock_t barrier_lock;
|
|
+ struct list_head barrier_queue;
|
|
+
|
|
+ struct lm_buckets *aggr_buckets;
|
|
+
|
|
+ struct latency_model latency_model[ADIOS_OPTYPES];
|
|
+ struct timer_list update_timer;
|
|
+
|
|
+ union adios_in_flight_rqs in_flight_rqs;
|
|
+ atomic64_t total_pred_lat;
|
|
+
|
|
+ struct adios_pcpu_completion __percpu *pcpu_completion;
|
|
+
|
|
+ struct kmem_cache *rq_data_cache;
|
|
+ mempool_t *rq_data_pool;
|
|
+ struct kmem_cache *dl_group_pool;
|
|
+
|
|
+ struct request_queue *queue;
|
|
+};
|
|
+
|
|
+// List of requests with the same deadline in the deadline-sorted tree
|
|
+struct dl_group {
|
|
+ struct rb_node node;
|
|
+ struct list_head rqs;
|
|
+ u64 deadline;
|
|
+} __attribute__((aligned(64)));
|
|
+
|
|
+// Structure to hold scheduler-specific data for each request
|
|
+struct adios_rq_data {
|
|
+ struct list_head *dl_group;
|
|
+ struct list_head dl_node;
|
|
+
|
|
+ struct request *rq;
|
|
+ u64 deadline;
|
|
+ u64 pred_lat;
|
|
+ u32 block_size;
|
|
+ bool managed;
|
|
+} __attribute__((aligned(64)));
|
|
+
|
|
+static const int adios_prio_to_wmult[40] = {
|
|
+ /* -20 */ 88761, 71755, 56483, 46273, 36291,
|
|
+ /* -15 */ 29154, 23254, 18705, 14949, 11916,
|
|
+ /* -10 */ 9548, 7620, 6100, 4904, 3906,
|
|
+ /* -5 */ 3121, 2501, 1991, 1586, 1277,
|
|
+ /* 0 */ 1024, 820, 655, 526, 423,
|
|
+ /* 5 */ 335, 272, 215, 172, 137,
|
|
+ /* 10 */ 110, 87, 70, 56, 45,
|
|
+ /* 15 */ 36, 29, 23, 18, 15,
|
|
+};
|
|
+
|
|
+static inline bool compliant(struct adios_data *ad, u32 flag) {
|
|
+ return ad->compliance_flags & flag;
|
|
+}
|
|
+
|
|
+// Count the number of entries in aggregated small buckets
|
|
+static u64 lm_count_small_entries(struct latency_bucket_small *buckets) {
|
|
+ u64 total_weight = 0;
|
|
+ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++)
|
|
+ total_weight += buckets[i].sum_of_weights;
|
|
+ return total_weight;
|
|
+}
|
|
+
|
|
+// Update the small buckets in the latency model from aggregated data
|
|
+static bool lm_update_small_buckets(struct latency_model *model,
|
|
+ struct latency_model_params *params,
|
|
+ struct latency_bucket_small *buckets,
|
|
+ u64 total_weight, bool count_all) {
|
|
+ u64 sum_latency = 0;
|
|
+ u64 sum_weight = 0;
|
|
+ u64 cumulative_weight = 0, threshold_weight = 0;
|
|
+ u8 outlier_threshold_bucket = 0;
|
|
+ u8 outlier_percentile = LM_OUTLIER_PERCENTILE;
|
|
+ u8 reduction;
|
|
+
|
|
+ if (count_all)
|
|
+ outlier_percentile = 100;
|
|
+
|
|
+ // Calculate the threshold weight for outlier detection
|
|
+ threshold_weight = (total_weight * outlier_percentile) / 100;
|
|
+
|
|
+ // Identify the bucket that corresponds to the outlier threshold
|
|
+ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) {
|
|
+ cumulative_weight += buckets[i].sum_of_weights;
|
|
+ if (cumulative_weight >= threshold_weight) {
|
|
+ outlier_threshold_bucket = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Calculate the average latency, excluding outliers
|
|
+ for (u8 i = 0; i <= outlier_threshold_bucket; i++) {
|
|
+ struct latency_bucket_small *bucket = &buckets[i];
|
|
+ if (i < outlier_threshold_bucket) {
|
|
+ sum_latency += bucket->weighted_sum_latency;
|
|
+ sum_weight += bucket->sum_of_weights;
|
|
+ } else {
|
|
+ // The threshold bucket's contribution is proportional
|
|
+ u64 remaining_weight =
|
|
+ threshold_weight - (cumulative_weight - bucket->sum_of_weights);
|
|
+ if (bucket->sum_of_weights > 0) {
|
|
+ sum_latency += div_u64(bucket->weighted_sum_latency *
|
|
+ remaining_weight, bucket->sum_of_weights);
|
|
+ sum_weight += remaining_weight;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Shrink the model if it reaches at the readjustment threshold
|
|
+ if (params->small_count >= 1000ULL * model->lm_shrink_at_kreqs) {
|
|
+ reduction = model->lm_shrink_resist;
|
|
+ if (params->small_count >> reduction) {
|
|
+ params->small_sum_delay -= params->small_sum_delay >> reduction;
|
|
+ params->small_count -= params->small_count >> reduction;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!sum_weight)
|
|
+ return false;
|
|
+
|
|
+ // Accumulate the average latency into the statistics
|
|
+ params->small_sum_delay += sum_latency;
|
|
+ params->small_count += sum_weight;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+// Count the number of entries in aggregated large buckets
|
|
+static u64 lm_count_large_entries(struct latency_bucket_large *buckets) {
|
|
+ u64 total_weight = 0;
|
|
+ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++)
|
|
+ total_weight += buckets[i].sum_of_weights;
|
|
+ return total_weight;
|
|
+}
|
|
+
|
|
+// Update the large buckets in the latency model from aggregated data
|
|
+static bool lm_update_large_buckets(struct latency_model *model,
|
|
+ struct latency_model_params *params,
|
|
+ struct latency_bucket_large *buckets,
|
|
+ u64 total_weight, bool count_all) {
|
|
+ s64 sum_latency = 0;
|
|
+ u64 sum_block_size = 0, intercept;
|
|
+ u64 cumulative_weight = 0, threshold_weight = 0;
|
|
+ u64 sum_weight = 0;
|
|
+ u8 outlier_threshold_bucket = 0;
|
|
+ u8 outlier_percentile = LM_OUTLIER_PERCENTILE;
|
|
+ u8 reduction;
|
|
+
|
|
+ if (count_all)
|
|
+ outlier_percentile = 100;
|
|
+
|
|
+ // Calculate the threshold weight for outlier detection
|
|
+ threshold_weight = (total_weight * outlier_percentile) / 100;
|
|
+
|
|
+ // Identify the bucket that corresponds to the outlier threshold
|
|
+ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) {
|
|
+ cumulative_weight += buckets[i].sum_of_weights;
|
|
+ if (cumulative_weight >= threshold_weight) {
|
|
+ outlier_threshold_bucket = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Calculate the average latency and block size, excluding outliers
|
|
+ for (u8 i = 0; i <= outlier_threshold_bucket; i++) {
|
|
+ struct latency_bucket_large *bucket = &buckets[i];
|
|
+ if (i < outlier_threshold_bucket) {
|
|
+ sum_latency += bucket->weighted_sum_latency;
|
|
+ sum_block_size += bucket->weighted_sum_block_size;
|
|
+ sum_weight += bucket->sum_of_weights;
|
|
+ } else {
|
|
+ // The threshold bucket's contribution is proportional
|
|
+ u64 remaining_weight =
|
|
+ threshold_weight - (cumulative_weight - bucket->sum_of_weights);
|
|
+ if (bucket->sum_of_weights > 0) {
|
|
+ sum_latency += div_u64(bucket->weighted_sum_latency *
|
|
+ remaining_weight, bucket->sum_of_weights);
|
|
+ sum_block_size += div_u64(bucket->weighted_sum_block_size *
|
|
+ remaining_weight, bucket->sum_of_weights);
|
|
+ sum_weight += remaining_weight;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!sum_weight)
|
|
+ return false;
|
|
+
|
|
+ // Shrink the model if it reaches at the readjustment threshold
|
|
+ if (params->large_sum_bsize >= 0x40000000ULL * model->lm_shrink_at_gbytes) {
|
|
+ reduction = model->lm_shrink_resist;
|
|
+ if (params->large_sum_bsize >> reduction) {
|
|
+ params->large_sum_delay -= params->large_sum_delay >> reduction;
|
|
+ params->large_sum_bsize -= params->large_sum_bsize >> reduction;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Accumulate the average delay into the statistics
|
|
+ intercept = params->base;
|
|
+ if (sum_latency > intercept)
|
|
+ sum_latency -= intercept;
|
|
+
|
|
+ params->large_sum_delay += sum_latency;
|
|
+ params->large_sum_bsize += sum_block_size;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static void reset_buckets(struct lm_buckets *buckets)
|
|
+{ memset(buckets, 0, sizeof(*buckets)); }
|
|
+
|
|
+static void lm_reset_pcpu_buckets(struct latency_model *model) {
|
|
+ int cpu;
|
|
+ for_each_possible_cpu(cpu) {
|
|
+ reset_buckets(per_cpu_ptr(model->pcpu_buckets, cpu));
|
|
+ reset_buckets(per_cpu_ptr(model->pcpu_snapshot, cpu));
|
|
+ }
|
|
+}
|
|
+
|
|
+// Update the latency model parameters and statistics
|
|
+static void latency_model_update(
|
|
+ struct adios_data *ad, struct latency_model *model) {
|
|
+ u64 now;
|
|
+ u64 small_weight, large_weight;
|
|
+ bool time_elapsed;
|
|
+ bool small_processed = false, large_processed = false;
|
|
+ struct lm_buckets *aggr = ad->aggr_buckets;
|
|
+ struct latency_bucket_small *asb;
|
|
+ struct latency_bucket_large *alb;
|
|
+ struct lm_buckets *pcpu_b, *snap;
|
|
+ unsigned long flags;
|
|
+ int cpu;
|
|
+ struct latency_model_params *old_params, *new_params;
|
|
+
|
|
+ spin_lock_irqsave(&model->update_lock, flags);
|
|
+
|
|
+ old_params = rcu_dereference_protected(model->params,
|
|
+ lockdep_is_held(&model->update_lock));
|
|
+ new_params = kmemdup(old_params, sizeof(*new_params), GFP_ATOMIC);
|
|
+ if (!new_params) {
|
|
+ spin_unlock_irqrestore(&model->update_lock, flags);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Aggregate deltas from all CPUs using snapshot-delta method.
|
|
+ // Per-CPU counters increase monotonically; we compute delta = current - snapshot.
|
|
+ for_each_possible_cpu(cpu) {
|
|
+ pcpu_b = per_cpu_ptr(model->pcpu_buckets, cpu);
|
|
+ snap = per_cpu_ptr(model->pcpu_snapshot, cpu);
|
|
+
|
|
+ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) {
|
|
+ u64 sw = pcpu_b->small_bucket[i].sum_of_weights;
|
|
+ u64 sl = pcpu_b->small_bucket[i].weighted_sum_latency;
|
|
+ u64 dsw = sw - snap->small_bucket[i].sum_of_weights;
|
|
+ u64 dsl = sl - snap->small_bucket[i].weighted_sum_latency;
|
|
+ snap->small_bucket[i].sum_of_weights = sw;
|
|
+ snap->small_bucket[i].weighted_sum_latency = sl;
|
|
+ if (dsw) {
|
|
+ asb = &aggr->small_bucket[i];
|
|
+ asb->sum_of_weights += dsw;
|
|
+ asb->weighted_sum_latency += dsl;
|
|
+ }
|
|
+
|
|
+ u64 lw = pcpu_b->large_bucket[i].sum_of_weights;
|
|
+ u64 ll = pcpu_b->large_bucket[i].weighted_sum_latency;
|
|
+ u64 lb = pcpu_b->large_bucket[i].weighted_sum_block_size;
|
|
+ u64 dlw = lw - snap->large_bucket[i].sum_of_weights;
|
|
+ u64 dll = ll - snap->large_bucket[i].weighted_sum_latency;
|
|
+ u64 dlb = lb - snap->large_bucket[i].weighted_sum_block_size;
|
|
+ snap->large_bucket[i].sum_of_weights = lw;
|
|
+ snap->large_bucket[i].weighted_sum_latency = ll;
|
|
+ snap->large_bucket[i].weighted_sum_block_size = lb;
|
|
+ if (dlw) {
|
|
+ alb = &aggr->large_bucket[i];
|
|
+ alb->sum_of_weights += dlw;
|
|
+ alb->weighted_sum_latency += dll;
|
|
+ alb->weighted_sum_block_size += dlb;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Count the number of entries in aggregated buckets
|
|
+ small_weight = lm_count_small_entries(aggr->small_bucket);
|
|
+ large_weight = lm_count_large_entries(aggr->large_bucket);
|
|
+
|
|
+ // Whether enough time has elapsed since the last update
|
|
+ now = jiffies;
|
|
+ time_elapsed = unlikely(!new_params->base) ||
|
|
+ new_params->last_update_jiffies +
|
|
+ msecs_to_jiffies(LM_INTERVAL_THRESHOLD) <= now;
|
|
+
|
|
+ // Update small buckets
|
|
+ if (small_weight && (time_elapsed ||
|
|
+ LM_SAMPLES_THRESHOLD <= small_weight || !new_params->base)) {
|
|
+ small_processed = lm_update_small_buckets(model, new_params,
|
|
+ aggr->small_bucket, small_weight, !new_params->base);
|
|
+ memset(&aggr->small_bucket[0], 0, sizeof(aggr->small_bucket));
|
|
+ }
|
|
+ // Update large buckets
|
|
+ if (large_weight && (time_elapsed ||
|
|
+ LM_SAMPLES_THRESHOLD <= large_weight || !new_params->slope)) {
|
|
+ large_processed = lm_update_large_buckets(model, new_params,
|
|
+ aggr->large_bucket, large_weight, !new_params->slope);
|
|
+ memset(&aggr->large_bucket[0], 0, sizeof(aggr->large_bucket));
|
|
+ }
|
|
+
|
|
+ // Update the base parameter if small bucket was processed
|
|
+ if (small_processed && likely(new_params->small_count))
|
|
+ new_params->base = div_u64(new_params->small_sum_delay,
|
|
+ new_params->small_count);
|
|
+
|
|
+ // Update the slope parameter if large bucket was processed
|
|
+ if (large_processed && likely(new_params->large_sum_bsize))
|
|
+ new_params->slope = div_u64(new_params->large_sum_delay,
|
|
+ DIV_ROUND_UP_ULL(new_params->large_sum_bsize, 1024));
|
|
+
|
|
+ // Update last updated jiffies if update happened or time has elapsed
|
|
+ if (small_processed || large_processed || time_elapsed)
|
|
+ new_params->last_update_jiffies = now;
|
|
+
|
|
+ rcu_assign_pointer(model->params, new_params);
|
|
+ spin_unlock_irqrestore(&model->update_lock, flags);
|
|
+
|
|
+ kfree_rcu(old_params, rcu);
|
|
+}
|
|
+
|
|
+// Determine the bucket index for a given measured and predicted latency
|
|
+static u8 lm_input_bucket_index(u64 measured, u64 predicted) {
|
|
+ u32 bucket_index;
|
|
+
|
|
+ if (measured < predicted * 2)
|
|
+ bucket_index = div_u64((measured * 20), predicted);
|
|
+ else if (measured < predicted * 5)
|
|
+ bucket_index = div_u64((measured * 10), predicted) + 20;
|
|
+ else
|
|
+ bucket_index = div_u64((measured * 3), predicted) + 40;
|
|
+
|
|
+ if (bucket_index >= LM_LAT_BUCKET_COUNT)
|
|
+ bucket_index = LM_LAT_BUCKET_COUNT - 1;
|
|
+
|
|
+ return (u8)bucket_index;
|
|
+}
|
|
+
|
|
+// Input latency data into the latency model
|
|
+static void latency_model_input(struct adios_data *ad,
|
|
+ struct latency_model *model,
|
|
+ u32 block_size, u64 latency, u64 pred_lat, u32 weight) {
|
|
+ unsigned long flags;
|
|
+ u8 bucket_index;
|
|
+ struct lm_buckets *buckets;
|
|
+ u64 current_base;
|
|
+ struct latency_model_params *params;
|
|
+
|
|
+ local_irq_save(flags);
|
|
+ buckets = per_cpu_ptr(model->pcpu_buckets, __smp_processor_id());
|
|
+
|
|
+ rcu_read_lock();
|
|
+ params = rcu_dereference(model->params);
|
|
+ current_base = params->base;
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ if (block_size <= LM_BLOCK_SIZE_THRESHOLD) {
|
|
+ // Handle small requests
|
|
+ bucket_index = lm_input_bucket_index(latency, current_base ?: 1);
|
|
+
|
|
+ buckets->small_bucket[bucket_index].sum_of_weights += weight;
|
|
+ buckets->small_bucket[bucket_index].weighted_sum_latency +=
|
|
+ latency * weight;
|
|
+
|
|
+ local_irq_restore(flags);
|
|
+
|
|
+ if (unlikely(!current_base)) {
|
|
+ latency_model_update(ad, model);
|
|
+ return;
|
|
+ }
|
|
+ } else {
|
|
+ // Handle large requests
|
|
+ if (!current_base || !pred_lat) {
|
|
+ local_irq_restore(flags);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ bucket_index = lm_input_bucket_index(latency, pred_lat);
|
|
+
|
|
+ buckets->large_bucket[bucket_index].sum_of_weights += weight;
|
|
+ buckets->large_bucket[bucket_index].weighted_sum_latency +=
|
|
+ latency * weight;
|
|
+ buckets->large_bucket[bucket_index].weighted_sum_block_size +=
|
|
+ block_size * weight;
|
|
+
|
|
+ local_irq_restore(flags);
|
|
+ }
|
|
+}
|
|
+
|
|
+// Predict the latency for a given block size using the latency model
|
|
+static u64 latency_model_predict(struct latency_model *model, u32 block_size) {
|
|
+ u64 result;
|
|
+ struct latency_model_params *params;
|
|
+
|
|
+ rcu_read_lock();
|
|
+ params = rcu_dereference(model->params);
|
|
+
|
|
+ result = params->base;
|
|
+ if (block_size > LM_BLOCK_SIZE_THRESHOLD)
|
|
+ result += params->slope *
|
|
+ DIV_ROUND_UP_ULL(block_size - LM_BLOCK_SIZE_THRESHOLD, 1024);
|
|
+
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+// Determine the type of operation based on request flags
|
|
+static u8 adios_optype(struct request *rq) {
|
|
+ switch (rq->cmd_flags & REQ_OP_MASK) {
|
|
+ case REQ_OP_READ:
|
|
+ return ADIOS_READ;
|
|
+ case REQ_OP_WRITE:
|
|
+ return ADIOS_WRITE;
|
|
+ case REQ_OP_DISCARD:
|
|
+ return ADIOS_DISCARD;
|
|
+ default:
|
|
+ return ADIOS_OTHER;
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline u8 adios_optype_not_read(struct request *rq) {
|
|
+ return (rq->cmd_flags & REQ_OP_MASK) != REQ_OP_READ;
|
|
+}
|
|
+
|
|
+// Helper function to retrieve adios_rq_data from a request
|
|
+static inline struct adios_rq_data *get_rq_data(struct request *rq) {
|
|
+ return rq->elv.priv[0];
|
|
+}
|
|
+
|
|
+static inline
|
|
+void set_adios_state(struct adios_data *ad, u32 shift, u32 idx, bool flag) {
|
|
+ if (flag)
|
|
+ atomic_or(1U << (idx + shift), &ad->state);
|
|
+ else
|
|
+ atomic_andnot(1U << (idx + shift), &ad->state);
|
|
+}
|
|
+
|
|
+static inline u32 get_adios_state(struct adios_data *ad)
|
|
+{ return atomic_read(&ad->state); }
|
|
+
|
|
+static inline u32 eval_this_adios_state(u32 state, u32 shift)
|
|
+{ return (state >> shift) & 0x3; }
|
|
+
|
|
+static inline u32 eval_adios_state(struct adios_data *ad, u32 shift)
|
|
+{ return eval_this_adios_state(get_adios_state(ad), shift); }
|
|
+
|
|
+// Add a request to the deadline-sorted red-black tree
|
|
+static void add_to_dl_tree(
|
|
+ struct adios_data *ad, bool dl_idx, struct request *rq) {
|
|
+ struct rb_root_cached *root = &ad->dl_tree[dl_idx];
|
|
+ struct rb_node **link = &(root->rb_root.rb_node), *parent = NULL;
|
|
+ bool leftmost = true;
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+ struct dl_group *dlg;
|
|
+ u64 deadline;
|
|
+ bool was_empty = RB_EMPTY_ROOT(&root->rb_root);
|
|
+
|
|
+ /* Tier-2: Synchronous Requests
|
|
+ * - Needs to be FIFO within a same optype
|
|
+ * - Relaxed order between different optypes
|
|
+ * - basically needs to be processed in early time */
|
|
+ rd->deadline = rq->start_time_ns;
|
|
+
|
|
+ /* Tier-3: Aynchronous Requests
|
|
+ * - Can be reordered and delayed freely */
|
|
+ if (!(rq->cmd_flags & REQ_SYNC)) {
|
|
+ rd->deadline += ad->latency_target[adios_optype(rq)];
|
|
+ if (!compliant(ad, ADIOS_CF_FIXORDER))
|
|
+ rd->deadline += rd->pred_lat;
|
|
+ }
|
|
+
|
|
+ // Now quantize the deadline (-> dlg->deadline == RB-Tree key)
|
|
+ deadline = rd->deadline & ~((1ULL << ADIOS_QUANTUM_SHIFT) - 1);
|
|
+
|
|
+ while (*link) {
|
|
+ dlg = rb_entry(*link, struct dl_group, node);
|
|
+ s64 diff = deadline - dlg->deadline;
|
|
+
|
|
+ parent = *link;
|
|
+ if (diff < 0) {
|
|
+ link = &((*link)->rb_left);
|
|
+ } else if (diff > 0) {
|
|
+ link = &((*link)->rb_right);
|
|
+ leftmost = false;
|
|
+ } else { // diff == 0
|
|
+ goto found;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dlg = rb_entry_safe(parent, struct dl_group, node);
|
|
+ if (!dlg || dlg->deadline != deadline) {
|
|
+ dlg = kmem_cache_zalloc(ad->dl_group_pool, GFP_ATOMIC);
|
|
+ if (!dlg)
|
|
+ return;
|
|
+ dlg->deadline = deadline;
|
|
+ INIT_LIST_HEAD(&dlg->rqs);
|
|
+ rb_link_node(&dlg->node, parent, link);
|
|
+ rb_insert_color_cached(&dlg->node, root, leftmost);
|
|
+ }
|
|
+found:
|
|
+ list_add_tail(&rd->dl_node, &dlg->rqs);
|
|
+ rd->dl_group = &dlg->rqs;
|
|
+
|
|
+ if (was_empty)
|
|
+ set_adios_state(ad, ADIOS_STATE_DL, dl_idx, true);
|
|
+}
|
|
+
|
|
+// Remove a request from the deadline-sorted red-black tree
|
|
+static void del_from_dl_tree(
|
|
+ struct adios_data *ad, bool dl_idx, struct request *rq) {
|
|
+ struct rb_root_cached *root = &ad->dl_tree[dl_idx];
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+ struct dl_group *dlg = container_of(rd->dl_group, struct dl_group, rqs);
|
|
+
|
|
+ list_del_init(&rd->dl_node);
|
|
+ if (list_empty(&dlg->rqs)) {
|
|
+ rb_erase_cached(&dlg->node, root);
|
|
+ kmem_cache_free(ad->dl_group_pool, dlg);
|
|
+ }
|
|
+ rd->dl_group = NULL;
|
|
+
|
|
+ if (RB_EMPTY_ROOT(&ad->dl_tree[dl_idx].rb_root))
|
|
+ set_adios_state(ad, ADIOS_STATE_DL, dl_idx, false);
|
|
+}
|
|
+
|
|
+// Remove a request from the scheduler
|
|
+static void remove_request(struct adios_data *ad, struct request *rq) {
|
|
+ bool dl_idx = adios_optype_not_read(rq);
|
|
+ struct request_queue *q = rq->q;
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+
|
|
+ list_del_init(&rq->queuelist);
|
|
+
|
|
+ // We might not be on the rbtree, if we are doing an insert merge
|
|
+ if (rd->dl_group)
|
|
+ del_from_dl_tree(ad, dl_idx, rq);
|
|
+
|
|
+ elv_rqhash_del(q, rq);
|
|
+ if (q->last_merge == rq)
|
|
+ q->last_merge = NULL;
|
|
+}
|
|
+
|
|
+// Convert a queue depth to the corresponding word depth for shallow allocation
|
|
+static int to_word_depth(struct blk_mq_hw_ctx *hctx, unsigned int qdepth) {
|
|
+ struct sbitmap_queue *bt = &hctx->sched_tags->bitmap_tags;
|
|
+ const unsigned int nrr = hctx->queue->nr_requests;
|
|
+
|
|
+ return ((qdepth << bt->sb.shift) + nrr - 1) / nrr;
|
|
+}
|
|
+
|
|
+// We limit the depth of request allocation for asynchronous and write requests
|
|
+static void adios_limit_depth(blk_opf_t opf, struct blk_mq_alloc_data *data) {
|
|
+ struct adios_data *ad = data->q->elevator->elevator_data;
|
|
+
|
|
+ // Do not throttle synchronous reads
|
|
+ if (op_is_sync(opf) && !op_is_write(opf))
|
|
+ return;
|
|
+
|
|
+ data->shallow_depth = to_word_depth(data->hctx, ad->async_depth);
|
|
+}
|
|
+
|
|
+// The number of requests in the queue was notified from the block layer
|
|
+static void adios_depth_updated(struct request_queue *q) {
|
|
+ struct adios_data *ad = q->elevator->elevator_data;
|
|
+
|
|
+ ad->async_depth = q->nr_requests;
|
|
+ blk_mq_set_min_shallow_depth(q, 1);
|
|
+}
|
|
+
|
|
+// Handle request merging after a merge operation
|
|
+static void adios_request_merged(struct request_queue *q, struct request *req,
|
|
+ enum elv_merge type) {
|
|
+ bool dl_idx = adios_optype_not_read(req);
|
|
+ struct adios_data *ad = q->elevator->elevator_data;
|
|
+
|
|
+ // Reposition request in the deadline-sorted tree
|
|
+ del_from_dl_tree(ad, dl_idx, req);
|
|
+ add_to_dl_tree(ad, dl_idx, req);
|
|
+}
|
|
+
|
|
+// Handle merging of requests after one has been merged into another
|
|
+static void adios_merged_requests(struct request_queue *q, struct request *req,
|
|
+ struct request *next) {
|
|
+ struct adios_data *ad = q->elevator->elevator_data;
|
|
+
|
|
+ lockdep_assert_held(&ad->lock);
|
|
+
|
|
+ // kill knowledge of next, this one is a goner
|
|
+ remove_request(ad, next);
|
|
+}
|
|
+
|
|
+// Try to merge a bio into an existing rq before associating it with an rq
|
|
+static bool adios_bio_merge(struct request_queue *q, struct bio *bio,
|
|
+ unsigned int nr_segs) {
|
|
+ unsigned long flags;
|
|
+ struct adios_data *ad = q->elevator->elevator_data;
|
|
+ struct request *free = NULL;
|
|
+ bool ret;
|
|
+
|
|
+ if (eval_adios_state(ad, ADIOS_STATE_BP))
|
|
+ return false;
|
|
+
|
|
+ if (!spin_trylock_irqsave(&ad->lock, flags))
|
|
+ return false;
|
|
+
|
|
+ ret = blk_mq_sched_try_merge(q, bio, nr_segs, &free);
|
|
+ spin_unlock_irqrestore(&ad->lock, flags);
|
|
+
|
|
+ if (free)
|
|
+ blk_mq_free_request(free);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static bool merge_or_insert_to_dl_tree(struct adios_data *ad,
|
|
+ struct request *rq, struct request_queue *q, struct list_head *free) {
|
|
+ if (blk_mq_sched_try_insert_merge(q, rq, free))
|
|
+ return true;
|
|
+
|
|
+ bool dl_idx = adios_optype_not_read(rq);
|
|
+ add_to_dl_tree(ad, dl_idx, rq);
|
|
+
|
|
+ if (rq_mergeable(rq)) {
|
|
+ elv_rqhash_add(q, rq);
|
|
+ if (!q->last_merge)
|
|
+ q->last_merge = rq;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static void insert_to_prio_queue(struct adios_data *ad,
|
|
+ struct request *rq, bool pq_idx) {
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+
|
|
+ /* We're sure that rd->managed == true */
|
|
+ union adios_in_flight_rqs ifr = {
|
|
+ .count = 1,
|
|
+ .total_pred_lat = rd->pred_lat,
|
|
+ };
|
|
+ atomic64_add(ifr.scalar, &ad->in_flight_rqs.atomic);
|
|
+
|
|
+ scoped_guard(spinlock_irqsave, &ad->pq_lock) {
|
|
+ bool was_empty = list_empty(&ad->prio_queue[pq_idx]);
|
|
+ list_add_tail(&rq->queuelist, &ad->prio_queue[pq_idx]);
|
|
+ if (was_empty)
|
|
+ set_adios_state(ad, ADIOS_STATE_PQ, pq_idx, true);
|
|
+ }
|
|
+}
|
|
+
|
|
+// Insert a request into the scheduler (after Read & Write models stabilized)
|
|
+static void insert_request_post_stability(struct blk_mq_hw_ctx *hctx,
|
|
+ struct request *rq, blk_insert_t insert_flags, struct list_head *free) {
|
|
+ struct request_queue *q = hctx->queue;
|
|
+ struct adios_data *ad = q->elevator->elevator_data;
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+ u8 optype = adios_optype(rq);
|
|
+ bool rq_is_flush;
|
|
+
|
|
+ rd->managed = true;
|
|
+ rd->block_size = blk_rq_bytes(rq);
|
|
+ rd->pred_lat =
|
|
+ latency_model_predict(&ad->latency_model[optype], rd->block_size);
|
|
+ if (unlikely(rd->pred_lat > ad->lat_model_latency_limit))
|
|
+ rd->pred_lat = ad->lat_model_latency_limit;
|
|
+
|
|
+ /* Tier-0: BLK_MQ_INSERT_AT_HEAD Requests */
|
|
+ if (insert_flags & BLK_MQ_INSERT_AT_HEAD) {
|
|
+ insert_to_prio_queue(ad, rq, 0);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Strict Barrier Handling for REQ_OP_FLUSH:
|
|
+ * If a flush request arrives, or if the scheduler is already in a
|
|
+ * barrier-pending state, all subsequent requests are diverted to a
|
|
+ * separate barrier_queue. This ensures that no new requests are processed
|
|
+ * until all work preceding the barrier is complete.
|
|
+ */
|
|
+ rq_is_flush = (rq->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH;
|
|
+ if (eval_adios_state(ad, ADIOS_STATE_BP) || rq_is_flush) {
|
|
+ scoped_guard(spinlock_irqsave, &ad->barrier_lock) {
|
|
+ if (rq_is_flush)
|
|
+ set_adios_state(ad, ADIOS_STATE_BP, 0, true);
|
|
+ list_add_tail(&rq->queuelist, &ad->barrier_queue);
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (merge_or_insert_to_dl_tree(ad, rq, q, free))
|
|
+ return;
|
|
+}
|
|
+
|
|
+// Insert a request into the scheduler (before Read & Write models stabilizes)
|
|
+static void insert_request_pre_stability(struct blk_mq_hw_ctx *hctx,
|
|
+ struct request *rq, blk_insert_t insert_flags, struct list_head *free) {
|
|
+ struct adios_data *ad = hctx->queue->elevator->elevator_data;
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+ u8 optype = adios_optype(rq);
|
|
+ u8 pq_idx = !(insert_flags & BLK_MQ_INSERT_AT_HEAD);
|
|
+ bool stable = false;
|
|
+
|
|
+ rd->managed = true;
|
|
+ rd->block_size = blk_rq_bytes(rq);
|
|
+ rd->pred_lat =
|
|
+ latency_model_predict(&ad->latency_model[optype], rd->block_size);
|
|
+ if (unlikely(rd->pred_lat > ad->lat_model_latency_limit))
|
|
+ rd->pred_lat = ad->lat_model_latency_limit;
|
|
+
|
|
+ insert_to_prio_queue(ad, rq, pq_idx);
|
|
+
|
|
+ rcu_read_lock();
|
|
+ if (rcu_dereference(ad->latency_model[ADIOS_READ].params)->base > 0 &&
|
|
+ rcu_dereference(ad->latency_model[ADIOS_WRITE].params)->base > 0)
|
|
+ stable = true;
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ if (stable)
|
|
+ ad->models_stable = true;
|
|
+}
|
|
+
|
|
+// Insert multiple requests into the scheduler
|
|
+static void adios_insert_requests(struct blk_mq_hw_ctx *hctx,
|
|
+ struct list_head *list,
|
|
+ blk_insert_t insert_flags) {
|
|
+ struct request_queue *q = hctx->queue;
|
|
+ struct adios_data *ad = q->elevator->elevator_data;
|
|
+ struct request *rq;
|
|
+ bool stop = false;
|
|
+ LIST_HEAD(free);
|
|
+
|
|
+ do {
|
|
+ scoped_guard(spinlock_irqsave, &ad->lock)
|
|
+ for (int i = 0; i < ADIOS_MAX_INSERTS_PER_LOCK; i++) {
|
|
+ if (list_empty(list)) {
|
|
+ stop = true;
|
|
+ break;
|
|
+ }
|
|
+ rq = list_first_entry(list, struct request, queuelist);
|
|
+ list_del_init(&rq->queuelist);
|
|
+ if (likely(ad->models_stable))
|
|
+ insert_request_post_stability(hctx, rq, insert_flags, &free);
|
|
+ else
|
|
+ insert_request_pre_stability(hctx, rq, insert_flags, &free);
|
|
+ }} while (!stop);
|
|
+
|
|
+ blk_mq_free_requests(&free);
|
|
+}
|
|
+
|
|
+// Prepare a request before it is inserted into the scheduler
|
|
+static void adios_prepare_request(struct request *rq) {
|
|
+ struct adios_data *ad = rq->q->elevator->elevator_data;
|
|
+ struct adios_rq_data *rd;
|
|
+
|
|
+ rd = mempool_alloc(ad->rq_data_pool, GFP_ATOMIC);
|
|
+ memset(rd, 0, sizeof(*rd));
|
|
+ rd->rq = rq;
|
|
+ rq->elv.priv[0] = rd;
|
|
+}
|
|
+
|
|
+static struct adios_rq_data *get_dl_first_rd(struct adios_data *ad, bool idx) {
|
|
+ struct rb_root_cached *root = &ad->dl_tree[idx];
|
|
+ struct rb_node *first = rb_first_cached(root);
|
|
+ struct dl_group *dl_group = rb_entry(first, struct dl_group, node);
|
|
+
|
|
+ return list_first_entry(&dl_group->rqs, struct adios_rq_data, dl_node);
|
|
+}
|
|
+
|
|
+// Comparison function for sorting requests by block address
|
|
+static int cmp_rq_pos(void *priv,
|
|
+ const struct list_head *a, const struct list_head *b) {
|
|
+ struct request *rq_a = list_entry(a, struct request, queuelist);
|
|
+ struct request *rq_b = list_entry(b, struct request, queuelist);
|
|
+ u64 pos_a = blk_rq_pos(rq_a);
|
|
+ u64 pos_b = blk_rq_pos(rq_b);
|
|
+
|
|
+ return (int)(pos_a > pos_b) - (int)(pos_a < pos_b);
|
|
+}
|
|
+
|
|
+#ifndef list_last_entry_or_null
|
|
+#define list_last_entry_or_null(ptr, type, member) \
|
|
+ (!list_empty(ptr) ? list_last_entry(ptr, type, member) : NULL)
|
|
+#endif
|
|
+
|
|
+// Update the elevator direction
|
|
+static void update_elv_direction(struct adios_data *ad) {
|
|
+ if (!ad->is_rotational)
|
|
+ return;
|
|
+
|
|
+ bool page = ad->bq_page;
|
|
+ struct list_head *q = &ad->batch_queue[page][1];
|
|
+ if (ad->bq_batch_order[page] < ADIOS_BO_ELEVATOR || list_empty(q)) {
|
|
+ ad->elv_direction = 0;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Get first and last request positions in the queue
|
|
+ struct request *rq_a = list_first_entry(q, struct request, queuelist);
|
|
+ struct request *rq_b = list_last_entry (q, struct request, queuelist);
|
|
+ u64 pos_a = blk_rq_pos(rq_a);
|
|
+ u64 pos_b = blk_rq_pos(rq_b);
|
|
+ u64 avg_rq_pos = (pos_a + pos_b) >> 1;
|
|
+
|
|
+ ad->elv_direction = !!(ad->head_pos > avg_rq_pos);
|
|
+}
|
|
+
|
|
+// Fill the batch queues with requests from the deadline-sorted red-black tree
|
|
+static bool fill_batch_queues(struct adios_data *ad, u64 tpl) {
|
|
+ struct adios_rq_data *rd;
|
|
+ struct request *rq;
|
|
+ struct list_head *dest_q;
|
|
+ u8 dest_idx;
|
|
+ u64 added_lat = 0;
|
|
+ u32 optype_count[ADIOS_OPTYPES] = {0};
|
|
+ u32 count = 0;
|
|
+ u8 optype;
|
|
+ bool page = !ad->bq_page, dl_idx, bias_idx, update_bias;
|
|
+ u32 dl_queued;
|
|
+ u8 bq_batch_order;
|
|
+ bool stop = false;
|
|
+
|
|
+ // Reset batch queue counts for the back page
|
|
+ memset(&ad->batch_count[page], 0, sizeof(ad->batch_count[page]));
|
|
+
|
|
+ ad->bq_batch_order[page] =
|
|
+ bq_batch_order = ad->batch_order;
|
|
+
|
|
+ do {
|
|
+ scoped_guard(spinlock_irqsave, &ad->lock)
|
|
+ for (int i = 0; i < ADIOS_MAX_DELETES_PER_LOCK; i++) {
|
|
+ bool has_base = false;
|
|
+
|
|
+ dl_queued = eval_adios_state(ad, ADIOS_STATE_DL);
|
|
+ // Check if there are any requests queued in the deadline tree
|
|
+ if (!dl_queued) {
|
|
+ stop = true;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ // Reads if both queues have requests, otherwise pick the non-empty.
|
|
+ dl_idx = dl_queued >> 1;
|
|
+
|
|
+ // Get the first request from the deadline-sorted tree
|
|
+ rd = get_dl_first_rd(ad, dl_idx);
|
|
+
|
|
+ bias_idx = ad->dl_bias < 0;
|
|
+ // If read and write requests are queued, choose one based on bias
|
|
+ if (dl_queued == 0x3) {
|
|
+ struct adios_rq_data *trd[2] = {get_dl_first_rd(ad, 0), rd};
|
|
+ rd = trd[bias_idx];
|
|
+
|
|
+ update_bias = (trd[bias_idx]->deadline > trd[!bias_idx]->deadline);
|
|
+ } else
|
|
+ update_bias = (bias_idx == dl_idx);
|
|
+
|
|
+ rq = rd->rq;
|
|
+ optype = adios_optype(rq);
|
|
+
|
|
+ rcu_read_lock();
|
|
+ has_base =
|
|
+ !!rcu_dereference(ad->latency_model[optype].params)->base;
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ // Check batch size and total predicted latency
|
|
+ if (count && (!has_base ||
|
|
+ ad->batch_count[page][optype] >= ad->batch_limit[optype] ||
|
|
+ (tpl + added_lat + rd->pred_lat) > ad->global_latency_window)) {
|
|
+ stop = true;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (update_bias) {
|
|
+ s64 sign = ((s64)bias_idx << 1) - 1;
|
|
+ if (unlikely(!rd->pred_lat))
|
|
+ ad->dl_bias = sign;
|
|
+ else
|
|
+ // Adjust the bias based on the predicted latency
|
|
+ ad->dl_bias += sign * (s64)((rd->pred_lat *
|
|
+ adios_prio_to_wmult[ad->dl_prio[bias_idx] + 20]) >> 10);
|
|
+ }
|
|
+
|
|
+ remove_request(ad, rq);
|
|
+
|
|
+ // Add request to the corresponding batch queue
|
|
+ dest_idx = (bq_batch_order == ADIOS_BO_OPTYPE || optype == ADIOS_OTHER)?
|
|
+ optype : !!(rd->deadline != rq->start_time_ns);
|
|
+ dest_q = &ad->batch_queue[page][dest_idx];
|
|
+ list_add_tail(&rq->queuelist, dest_q);
|
|
+ ad->bq_state[page] |= 1U << dest_idx;
|
|
+ ad->batch_count[page][optype]++;
|
|
+ optype_count[optype]++;
|
|
+ added_lat += rd->pred_lat;
|
|
+ count++;
|
|
+ }} while (!stop);
|
|
+
|
|
+ if (bq_batch_order == ADIOS_BO_ELEVATOR && ad->batch_count[page][1] > 1)
|
|
+ list_sort(NULL, &ad->batch_queue[page][1], cmp_rq_pos);
|
|
+
|
|
+ if (count) {
|
|
+ /* We're sure that every request's rd->managed == true */
|
|
+ union adios_in_flight_rqs ifr = {
|
|
+ .count = count,
|
|
+ .total_pred_lat = added_lat,
|
|
+ };
|
|
+ atomic64_add(ifr.scalar, &ad->in_flight_rqs.atomic);
|
|
+
|
|
+ set_adios_state(ad, ADIOS_STATE_BQ, page, true);
|
|
+
|
|
+ for (optype = 0; optype < ADIOS_OPTYPES; optype++)
|
|
+ if (ad->batch_actual_max_size[optype] < optype_count[optype])
|
|
+ ad->batch_actual_max_size[optype] = optype_count[optype];
|
|
+ if (ad->batch_actual_max_total < count)
|
|
+ ad->batch_actual_max_total = count;
|
|
+ }
|
|
+ return count;
|
|
+}
|
|
+
|
|
+// Flip to the next batch queue page
|
|
+static void flip_bq_page(struct adios_data *ad) {
|
|
+ ad->bq_page = !ad->bq_page;
|
|
+ update_elv_direction(ad);
|
|
+}
|
|
+
|
|
+// Pop a request from the specified index (optype or elevator tier)
|
|
+static inline struct request *pop_bq_request(
|
|
+ struct adios_data *ad, u8 idx, bool direction) {
|
|
+ bool page = ad->bq_page;
|
|
+ struct list_head *q = &ad->batch_queue[page][idx];
|
|
+ struct request *rq = direction ?
|
|
+ list_last_entry_or_null (q, struct request, queuelist):
|
|
+ list_first_entry_or_null(q, struct request, queuelist);
|
|
+ if (rq) {
|
|
+ list_del_init(&rq->queuelist);
|
|
+ if (list_empty(q))
|
|
+ ad->bq_state[page] &= ~(1U << idx);
|
|
+ }
|
|
+ return rq;
|
|
+}
|
|
+
|
|
+static struct request *pop_next_bq_request_optype(struct adios_data *ad) {
|
|
+ u32 bq_state = ad->bq_state[ad->bq_page];
|
|
+ if (!bq_state) return NULL;
|
|
+
|
|
+ struct request *rq;
|
|
+ u32 bq_idx = __builtin_ctz(bq_state);
|
|
+
|
|
+ // Dispatch based on optype (FIFO within each) or single-queue elevator
|
|
+ rq = pop_bq_request(ad, bq_idx, false);
|
|
+ return rq;
|
|
+}
|
|
+
|
|
+static struct request *pop_next_bq_request_elevator(struct adios_data *ad) {
|
|
+ u32 bq_state = ad->bq_state[ad->bq_page];
|
|
+ if (!bq_state) return NULL;
|
|
+
|
|
+ struct request *rq;
|
|
+ u32 bq_idx = __builtin_ctz(bq_state);
|
|
+ bool direction = (bq_idx == 1) & ad->elv_direction;
|
|
+
|
|
+ // Tier-2 (sync) is always high priority
|
|
+ // Tier-3 (async) uses the pre-calculated elevator direction
|
|
+ rq = pop_bq_request(ad, bq_idx, direction);
|
|
+
|
|
+ /* If batch queue for the sync requests just became empty */
|
|
+ if (bq_idx == 0 && rq && !(bq_state & 0x1))
|
|
+ update_elv_direction(ad);
|
|
+
|
|
+ return rq;
|
|
+}
|
|
+
|
|
+// Returns the state of the batch queue page
|
|
+static inline bool bq_page_has_rq(u32 bq_state, bool page) {
|
|
+ return bq_state & (1U << page);
|
|
+}
|
|
+
|
|
+// Dispatch a request from the batch queues
|
|
+static struct request *dispatch_from_bq(struct adios_data *ad) {
|
|
+ struct request *rq;
|
|
+
|
|
+ guard(spinlock_irqsave)(&ad->bq_lock);
|
|
+
|
|
+ u32 state = get_adios_state(ad);
|
|
+ u32 bq_state = eval_this_adios_state(state, ADIOS_STATE_BQ);
|
|
+ u32 bq_curr_page_has_rq = bq_page_has_rq(bq_state, ad->bq_page);
|
|
+ union adios_in_flight_rqs ifr;
|
|
+ ifr.scalar = atomic64_read(&ad->in_flight_rqs.atomic);
|
|
+ u64 tpl = ifr.total_pred_lat;
|
|
+
|
|
+ // Refill the batch queues if the back page is empty, dl_tree has work, and
|
|
+ // current page is empty or the total ongoing latency is below the threshold
|
|
+ if (!bq_page_has_rq(bq_state, !ad->bq_page) &&
|
|
+ (!bq_curr_page_has_rq || (!tpl || tpl < div_u64(
|
|
+ ad->global_latency_window * ad->bq_refill_below_ratio, 100))) &&
|
|
+ eval_this_adios_state(state, ADIOS_STATE_DL))
|
|
+ fill_batch_queues(ad, tpl);
|
|
+
|
|
+ // If current batch queue page is empty, and the other page has work, flip
|
|
+ if (!bq_curr_page_has_rq &&
|
|
+ bq_page_has_rq(eval_adios_state(ad, ADIOS_STATE_BQ), !ad->bq_page))
|
|
+ flip_bq_page(ad);
|
|
+
|
|
+ // Use the per-page state to decide the dispatch logic, ensuring correctness
|
|
+ rq = (ad->bq_batch_order[ad->bq_page] == ADIOS_BO_ELEVATOR) ?
|
|
+ pop_next_bq_request_elevator(ad):
|
|
+ pop_next_bq_request_optype(ad);
|
|
+
|
|
+ if (rq) {
|
|
+ bool page = ad->bq_page;
|
|
+ bool is_empty = !ad->bq_state[page];
|
|
+ if (is_empty)
|
|
+ set_adios_state(ad, ADIOS_STATE_BQ, page, false);
|
|
+ return rq;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+// Dispatch a request from the priority queue
|
|
+static struct request *dispatch_from_pq(struct adios_data *ad) {
|
|
+ struct request *rq = NULL;
|
|
+
|
|
+ guard(spinlock_irqsave)(&ad->pq_lock);
|
|
+ u32 pq_state = eval_adios_state(ad, ADIOS_STATE_PQ);
|
|
+ u8 pq_idx = pq_state >> 1;
|
|
+ struct list_head *q = &ad->prio_queue[pq_idx];
|
|
+
|
|
+ if (unlikely(list_empty(q))) return NULL;
|
|
+
|
|
+ rq = list_first_entry(q, struct request, queuelist);
|
|
+ list_del_init(&rq->queuelist);
|
|
+ if (list_empty(q)) {
|
|
+ set_adios_state(ad, ADIOS_STATE_PQ, pq_idx, false);
|
|
+ update_elv_direction(ad);
|
|
+ }
|
|
+ return rq;
|
|
+}
|
|
+
|
|
+static bool release_barrier_requests(struct adios_data *ad) {
|
|
+ u32 moved_count = 0;
|
|
+ LIST_HEAD(local_list);
|
|
+
|
|
+ scoped_guard(spinlock_irqsave, &ad->barrier_lock) {
|
|
+ if (!list_empty(&ad->barrier_queue)) {
|
|
+ struct request *trq, *next;
|
|
+ bool first_barrier_moved = false;
|
|
+
|
|
+ list_for_each_entry_safe(trq, next, &ad->barrier_queue, queuelist) {
|
|
+ if (!first_barrier_moved) {
|
|
+ list_del_init(&trq->queuelist);
|
|
+ insert_to_prio_queue(ad, trq, 1);
|
|
+ moved_count++;
|
|
+ first_barrier_moved = true;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if ((trq->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH)
|
|
+ break;
|
|
+
|
|
+ list_move_tail(&trq->queuelist, &local_list);
|
|
+ moved_count++;
|
|
+ }
|
|
+
|
|
+ if (list_empty(&ad->barrier_queue))
|
|
+ set_adios_state(ad, ADIOS_STATE_BP, 0, false);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!moved_count)
|
|
+ return false;
|
|
+
|
|
+ if (!list_empty(&local_list)) {
|
|
+ struct request *trq, *next;
|
|
+ LIST_HEAD(free_list);
|
|
+
|
|
+ /* ad->lock is already held */
|
|
+ list_for_each_entry_safe(trq, next, &local_list, queuelist) {
|
|
+ list_del_init(&trq->queuelist);
|
|
+ if (merge_or_insert_to_dl_tree(ad, trq, ad->queue, &free_list))
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!list_empty(&free_list))
|
|
+ blk_mq_free_requests(&free_list);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+// Dispatch a request to the hardware queue
|
|
+static struct request *adios_dispatch_request(struct blk_mq_hw_ctx *hctx) {
|
|
+ struct adios_data *ad = hctx->queue->elevator->elevator_data;
|
|
+ struct request *rq;
|
|
+
|
|
+retry:
|
|
+ rq = dispatch_from_pq(ad);
|
|
+ if (rq)
|
|
+ goto found;
|
|
+
|
|
+ rq = dispatch_from_bq(ad);
|
|
+ if (rq)
|
|
+ goto found;
|
|
+
|
|
+ /*
|
|
+ * If all active queues are empty, check if we need to process a barrier.
|
|
+ * This is the trigger to release requests that were held in barrier_queue
|
|
+ * due to a REQ_OP_FLUSH barrier.
|
|
+ */
|
|
+ if (eval_adios_state(ad, ADIOS_STATE_BP)) {
|
|
+ bool barrier_released = false;
|
|
+ scoped_guard(spinlock_irqsave, &ad->lock)
|
|
+ barrier_released = release_barrier_requests(ad);
|
|
+ if (barrier_released)
|
|
+ goto retry;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+found:
|
|
+ if (ad->is_rotational)
|
|
+ ad->head_pos = blk_rq_pos(rq) + blk_rq_sectors(rq);
|
|
+
|
|
+ rq->rq_flags |= RQF_STARTED;
|
|
+ return rq;
|
|
+}
|
|
+
|
|
+// Timer callback function to periodically update latency models
|
|
+static void update_timer_callback(struct timer_list *t) {
|
|
+ struct adios_data *ad = timer_container_of(ad, t, update_timer);
|
|
+
|
|
+ for (u8 optype = 0; optype < ADIOS_OPTYPES; optype++)
|
|
+ latency_model_update(ad, &ad->latency_model[optype]);
|
|
+}
|
|
+
|
|
+// Handle the completion of a request
|
|
+static void adios_completed_request(struct request *rq, u64 now) {
|
|
+ struct adios_data *ad = rq->q->elevator->elevator_data;
|
|
+ struct adios_rq_data *rd = get_rq_data(rq);
|
|
+ union adios_in_flight_rqs ifr = { .scalar = 0 };
|
|
+
|
|
+ if (rd->managed) {
|
|
+ union adios_in_flight_rqs ifr_to_sub = {
|
|
+ .count = 1,
|
|
+ .total_pred_lat = rd->pred_lat,
|
|
+ };
|
|
+ ifr.scalar = atomic64_sub_return(
|
|
+ ifr_to_sub.scalar, &ad->in_flight_rqs.atomic);
|
|
+ }
|
|
+ u8 optype = adios_optype(rq);
|
|
+
|
|
+ unsigned long flags;
|
|
+ struct adios_pcpu_completion *pc;
|
|
+
|
|
+ local_irq_save(flags);
|
|
+ pc = this_cpu_ptr(ad->pcpu_completion);
|
|
+
|
|
+ if (optype == ADIOS_OTHER) {
|
|
+ // Non-positional commands make the head position unpredictable.
|
|
+ // Invalidate our knowledge of the last completed position.
|
|
+ if (ad->is_rotational)
|
|
+ pc->last_completed_pos = 0;
|
|
+ local_irq_restore(flags);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ u64 lct = pc->last_completed_time ?: rq->io_start_time_ns;
|
|
+ pc->last_completed_time = (ifr.count) ? now : 0;
|
|
+
|
|
+ if (!rq->io_start_time_ns || !rd->block_size || unlikely(now < lct)) {
|
|
+ local_irq_restore(flags);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ u64 latency = now - lct;
|
|
+
|
|
+ u32 weight = 1;
|
|
+ if (ad->is_rotational) {
|
|
+ sector_t current_pos = blk_rq_pos(rq);
|
|
+ // Only calculate seek distance if we have a valid last position.
|
|
+ if (pc->last_completed_pos > 0) {
|
|
+ u64 seek_distance = abs(
|
|
+ (s64)current_pos - (s64)pc->last_completed_pos);
|
|
+ if (seek_distance)
|
|
+ weight = 65 - __builtin_clzll(seek_distance);
|
|
+ }
|
|
+ // Update (or re-synchronize) our knowledge of the head position.
|
|
+ pc->last_completed_pos = current_pos + blk_rq_sectors(rq);
|
|
+ }
|
|
+
|
|
+ local_irq_restore(flags);
|
|
+
|
|
+ if (latency > ad->lat_model_latency_limit)
|
|
+ return;
|
|
+
|
|
+ latency_model_input(ad, &ad->latency_model[optype],
|
|
+ rd->block_size, latency, rd->pred_lat, weight);
|
|
+ timer_reduce(&ad->update_timer, jiffies + msecs_to_jiffies(100));
|
|
+}
|
|
+
|
|
+// Clean up after a request is finished
|
|
+static void adios_finish_request(struct request *rq) {
|
|
+ struct adios_data *ad = rq->q->elevator->elevator_data;
|
|
+
|
|
+ if (rq->elv.priv[0]) {
|
|
+ // Free adios_rq_data back to the memory pool
|
|
+ mempool_free(get_rq_data(rq), ad->rq_data_pool);
|
|
+ rq->elv.priv[0] = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+// Check if there are any requests available for dispatch
|
|
+static bool adios_has_work(struct blk_mq_hw_ctx *hctx) {
|
|
+ struct adios_data *ad = hctx->queue->elevator->elevator_data;
|
|
+
|
|
+ return atomic_read(&ad->state) != 0;
|
|
+}
|
|
+
|
|
+// Initialize the scheduler-specific data when initializing the request queue
|
|
+static int adios_init_sched(struct request_queue *q, struct elevator_queue *eq) {
|
|
+ struct adios_data *ad;
|
|
+ int ret = -ENOMEM;
|
|
+ u8 optype = 0;
|
|
+
|
|
+ ad = kzalloc_node(sizeof(*ad), GFP_KERNEL, q->node);
|
|
+ if (!ad) {
|
|
+ pr_err("adios: Failed to create adios_data\n");
|
|
+ goto put_eq;
|
|
+ }
|
|
+
|
|
+ eq->elevator_data = ad;
|
|
+
|
|
+ // Create a memory pool for adios_rq_data
|
|
+ ad->rq_data_cache = kmem_cache_create("adios_rq_data",
|
|
+ sizeof(struct adios_rq_data),
|
|
+ 0, SLAB_HWCACHE_ALIGN, NULL);
|
|
+ if (!ad->rq_data_cache) {
|
|
+ pr_err("adios: Failed to create rq_data_cache\n");
|
|
+ goto free_ad;
|
|
+ }
|
|
+
|
|
+ ad->rq_data_pool = mempool_create_slab_pool(
|
|
+ q->nr_requests, ad->rq_data_cache);
|
|
+ if (!ad->rq_data_pool) {
|
|
+ pr_err("adios: Failed to create rq_data_pool\n");
|
|
+ goto destroy_rq_data_cache;
|
|
+ }
|
|
+
|
|
+ /* Create a memory pool for dl_group */
|
|
+ ad->dl_group_pool = kmem_cache_create("dl_group_pool",
|
|
+ sizeof(struct dl_group),
|
|
+ 0, SLAB_HWCACHE_ALIGN, NULL);
|
|
+ if (!ad->dl_group_pool) {
|
|
+ pr_err("adios: Failed to create dl_group_pool\n");
|
|
+ goto destroy_rq_data_pool;
|
|
+ }
|
|
+
|
|
+ for (int i = 0; i < ADIOS_PQ_LEVELS; i++)
|
|
+ INIT_LIST_HEAD(&ad->prio_queue[i]);
|
|
+
|
|
+ for (u8 i = 0; i < ADIOS_DL_TYPES; i++) {
|
|
+ ad->dl_tree[i] = RB_ROOT_CACHED;
|
|
+ ad->dl_prio[i] = default_dl_prio[i];
|
|
+ }
|
|
+ ad->dl_bias = 0;
|
|
+
|
|
+ for (u8 page = 0; page < ADIOS_BQ_PAGES; page++)
|
|
+ for (optype = 0; optype < ADIOS_OPTYPES; optype++)
|
|
+ INIT_LIST_HEAD(&ad->batch_queue[page][optype]);
|
|
+
|
|
+ ad->aggr_buckets = kzalloc(sizeof(*ad->aggr_buckets), GFP_KERNEL);
|
|
+ if (!ad->aggr_buckets) {
|
|
+ pr_err("adios: Failed to allocate aggregation buckets\n");
|
|
+ goto destroy_dl_group_pool;
|
|
+ }
|
|
+
|
|
+ ad->pcpu_completion = alloc_percpu(struct adios_pcpu_completion);
|
|
+ if (!ad->pcpu_completion) {
|
|
+ pr_err("adios: Failed to allocate per-CPU completion data\n");
|
|
+ goto free_aggr_buckets;
|
|
+ }
|
|
+
|
|
+ for (optype = 0; optype < ADIOS_OPTYPES; optype++) {
|
|
+ struct latency_model *model = &ad->latency_model[optype];
|
|
+ struct latency_model_params *params;
|
|
+
|
|
+ spin_lock_init(&model->update_lock);
|
|
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
|
|
+ if (!params) {
|
|
+ pr_err("adios: Failed to allocate latency_model_params\n");
|
|
+ goto free_buckets;
|
|
+ }
|
|
+ params->last_update_jiffies = jiffies;
|
|
+ RCU_INIT_POINTER(model->params, params);
|
|
+
|
|
+ model->pcpu_buckets = alloc_percpu(struct lm_buckets);
|
|
+ if (!model->pcpu_buckets) {
|
|
+ pr_err("adios: Failed to allocate per-CPU buckets\n");
|
|
+ kfree(params);
|
|
+ goto free_buckets;
|
|
+ }
|
|
+
|
|
+ model->pcpu_snapshot = alloc_percpu(struct lm_buckets);
|
|
+ if (!model->pcpu_snapshot) {
|
|
+ pr_err("adios: Failed to allocate per-CPU snapshot\n");
|
|
+ free_percpu(model->pcpu_buckets);
|
|
+ kfree(params);
|
|
+ goto free_buckets;
|
|
+ }
|
|
+
|
|
+ model->lm_shrink_at_kreqs = default_lm_shrink_at_kreqs;
|
|
+ model->lm_shrink_at_gbytes = default_lm_shrink_at_gbytes;
|
|
+ model->lm_shrink_resist = default_lm_shrink_resist;
|
|
+ }
|
|
+
|
|
+ for (optype = 0; optype < ADIOS_OPTYPES; optype++) {
|
|
+ ad->latency_target[optype] = default_latency_target[optype];
|
|
+ ad->batch_limit[optype] = default_batch_limit[optype];
|
|
+ }
|
|
+
|
|
+ eq->elevator_data = ad;
|
|
+
|
|
+ ad->is_rotational = !!(q->limits.features & BLK_FEAT_ROTATIONAL);
|
|
+ ad->global_latency_window = (ad->is_rotational)?
|
|
+ default_global_latency_window_rotational:
|
|
+ default_global_latency_window;
|
|
+ ad->bq_refill_below_ratio = default_bq_refill_below_ratio;
|
|
+ ad->lat_model_latency_limit = default_lat_model_latency_limit;
|
|
+ ad->batch_order = default_batch_order;
|
|
+ ad->compliance_flags = default_compliance_flags;
|
|
+
|
|
+ ad->models_stable = false;
|
|
+
|
|
+ atomic_set(&ad->state, 0);
|
|
+
|
|
+ spin_lock_init(&ad->lock);
|
|
+ spin_lock_init(&ad->pq_lock);
|
|
+ spin_lock_init(&ad->bq_lock);
|
|
+ spin_lock_init(&ad->barrier_lock);
|
|
+ INIT_LIST_HEAD(&ad->barrier_queue);
|
|
+
|
|
+ timer_setup(&ad->update_timer, update_timer_callback, 0);
|
|
+
|
|
+ /* We dispatch from request queue wide instead of hw queue */
|
|
+ blk_queue_flag_set(QUEUE_FLAG_SQ_SCHED, q);
|
|
+
|
|
+ ad->queue = q;
|
|
+ blk_stat_enable_accounting(q);
|
|
+
|
|
+ q->elevator = eq;
|
|
+ adios_depth_updated(q);
|
|
+ return 0;
|
|
+
|
|
+free_buckets:
|
|
+ pr_err("adios: Failed to allocate per-cpu buckets\n");
|
|
+ while (optype-- > 0) {
|
|
+ struct latency_model *prev_model = &ad->latency_model[optype];
|
|
+ kfree(rcu_access_pointer(prev_model->params));
|
|
+ free_percpu(prev_model->pcpu_snapshot);
|
|
+ free_percpu(prev_model->pcpu_buckets);
|
|
+ }
|
|
+ free_percpu(ad->pcpu_completion);
|
|
+free_aggr_buckets:
|
|
+ kfree(ad->aggr_buckets);
|
|
+destroy_dl_group_pool:
|
|
+ kmem_cache_destroy(ad->dl_group_pool);
|
|
+destroy_rq_data_pool:
|
|
+ mempool_destroy(ad->rq_data_pool);
|
|
+destroy_rq_data_cache:
|
|
+ kmem_cache_destroy(ad->rq_data_cache);
|
|
+free_ad:
|
|
+ kfree(ad);
|
|
+put_eq:
|
|
+ kobject_put(&eq->kobj);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+// Clean up and free resources when exiting the scheduler
|
|
+static void adios_exit_sched(struct elevator_queue *e) {
|
|
+ struct adios_data *ad = e->elevator_data;
|
|
+
|
|
+ timer_shutdown_sync(&ad->update_timer);
|
|
+
|
|
+ WARN_ON_ONCE(!list_empty(&ad->barrier_queue));
|
|
+ for (int i = 0; i < 2; i++)
|
|
+ WARN_ON_ONCE(!list_empty(&ad->prio_queue[i]));
|
|
+
|
|
+ for (u8 i = 0; i < ADIOS_OPTYPES; i++) {
|
|
+ struct latency_model *model = &ad->latency_model[i];
|
|
+ struct latency_model_params *params = rcu_access_pointer(model->params);
|
|
+
|
|
+ RCU_INIT_POINTER(model->params, NULL);
|
|
+ kfree_rcu(params, rcu);
|
|
+
|
|
+ free_percpu(model->pcpu_snapshot);
|
|
+ free_percpu(model->pcpu_buckets);
|
|
+ }
|
|
+
|
|
+ synchronize_rcu();
|
|
+
|
|
+ free_percpu(ad->pcpu_completion);
|
|
+ kfree(ad->aggr_buckets);
|
|
+
|
|
+ mempool_destroy(ad->rq_data_pool);
|
|
+ kmem_cache_destroy(ad->rq_data_cache);
|
|
+
|
|
+ if (ad->dl_group_pool)
|
|
+ kmem_cache_destroy(ad->dl_group_pool);
|
|
+
|
|
+ blk_stat_disable_accounting(ad->queue);
|
|
+
|
|
+ kfree(ad);
|
|
+}
|
|
+
|
|
+static void sideload_latency_model(
|
|
+ struct latency_model *model, u64 base, u64 slope) {
|
|
+ struct latency_model_params *old_params, *new_params;
|
|
+ unsigned long flags;
|
|
+
|
|
+ new_params = kzalloc(sizeof(*new_params), GFP_KERNEL);
|
|
+ if (!new_params)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&model->update_lock, flags);
|
|
+
|
|
+ old_params = rcu_dereference_protected(model->params,
|
|
+ lockdep_is_held(&model->update_lock));
|
|
+
|
|
+ new_params->last_update_jiffies = jiffies;
|
|
+
|
|
+ // Initialize base and its statistics as a single sample.
|
|
+ new_params->base = base;
|
|
+ new_params->small_sum_delay = base;
|
|
+ new_params->small_count = 1;
|
|
+
|
|
+ // Initialize slope and its statistics as a single sample.
|
|
+ new_params->slope = slope;
|
|
+ new_params->large_sum_delay = slope;
|
|
+ new_params->large_sum_bsize = 1024; /* Corresponds to 1 KiB */
|
|
+
|
|
+ lm_reset_pcpu_buckets(model);
|
|
+
|
|
+ rcu_assign_pointer(model->params, new_params);
|
|
+ spin_unlock_irqrestore(&model->update_lock, flags);
|
|
+
|
|
+ kfree_rcu(old_params, rcu);
|
|
+}
|
|
+
|
|
+// Define sysfs attributes for operation types
|
|
+#define SYSFS_OPTYPE_DECL(name, optype) \
|
|
+static ssize_t adios_lat_model_##name##_show( \
|
|
+ struct elevator_queue *e, char *page) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ struct latency_model *model = &ad->latency_model[optype]; \
|
|
+ struct latency_model_params *params; \
|
|
+ ssize_t len = 0; \
|
|
+ u64 base, slope; \
|
|
+ rcu_read_lock(); \
|
|
+ params = rcu_dereference(model->params); \
|
|
+ base = params->base; \
|
|
+ slope = params->slope; \
|
|
+ rcu_read_unlock(); \
|
|
+ len += sprintf(page, "base : %llu ns\n", base); \
|
|
+ len += sprintf(page + len, "slope: %llu ns/KiB\n", slope); \
|
|
+ return len; \
|
|
+} \
|
|
+static ssize_t adios_lat_model_##name##_store( \
|
|
+ struct elevator_queue *e, const char *page, size_t count) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ struct latency_model *model = &ad->latency_model[optype]; \
|
|
+ u64 base, slope; \
|
|
+ int ret; \
|
|
+ ret = sscanf(page, "%llu %llu", &base, &slope); \
|
|
+ if (ret != 2) \
|
|
+ return -EINVAL; \
|
|
+ sideload_latency_model(model, base, slope); \
|
|
+ reset_buckets(ad->aggr_buckets); \
|
|
+ return count; \
|
|
+} \
|
|
+static ssize_t adios_lat_target_##name##_show( \
|
|
+ struct elevator_queue *e, char *page) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ return sprintf(page, "%llu\n", ad->latency_target[optype]); \
|
|
+} \
|
|
+static ssize_t adios_lat_target_##name##_store( \
|
|
+ struct elevator_queue *e, const char *page, size_t count) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ unsigned long nsec; \
|
|
+ int ret; \
|
|
+ ret = kstrtoul(page, 10, &nsec); \
|
|
+ if (ret) \
|
|
+ return ret; \
|
|
+ sideload_latency_model(&ad->latency_model[optype], 0, 0); \
|
|
+ ad->latency_target[optype] = nsec; \
|
|
+ return count; \
|
|
+} \
|
|
+static ssize_t adios_batch_limit_##name##_show( \
|
|
+ struct elevator_queue *e, char *page) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ return sprintf(page, "%u\n", ad->batch_limit[optype]); \
|
|
+} \
|
|
+static ssize_t adios_batch_limit_##name##_store( \
|
|
+ struct elevator_queue *e, const char *page, size_t count) { \
|
|
+ unsigned long max_batch; \
|
|
+ int ret; \
|
|
+ ret = kstrtoul(page, 10, &max_batch); \
|
|
+ if (ret || max_batch == 0) \
|
|
+ return -EINVAL; \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ ad->batch_limit[optype] = max_batch; \
|
|
+ return count; \
|
|
+}
|
|
+
|
|
+SYSFS_OPTYPE_DECL(read, ADIOS_READ);
|
|
+SYSFS_OPTYPE_DECL(write, ADIOS_WRITE);
|
|
+SYSFS_OPTYPE_DECL(discard, ADIOS_DISCARD);
|
|
+
|
|
+// Show the maximum batch size actually achieved for each operation type
|
|
+static ssize_t adios_batch_actual_max_show(
|
|
+ struct elevator_queue *e, char *page) {
|
|
+ struct adios_data *ad = e->elevator_data;
|
|
+ u32 total_count, read_count, write_count, discard_count;
|
|
+
|
|
+ total_count = ad->batch_actual_max_total;
|
|
+ read_count = ad->batch_actual_max_size[ADIOS_READ];
|
|
+ write_count = ad->batch_actual_max_size[ADIOS_WRITE];
|
|
+ discard_count = ad->batch_actual_max_size[ADIOS_DISCARD];
|
|
+
|
|
+ return sprintf(page,
|
|
+ "Total : %u\nDiscard: %u\nRead : %u\nWrite : %u\n",
|
|
+ total_count, discard_count, read_count, write_count);
|
|
+}
|
|
+
|
|
+#define SYSFS_ULL_DECL(field, min_val, max_val) \
|
|
+static ssize_t adios_##field##_show( \
|
|
+ struct elevator_queue *e, char *page) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ return sprintf(page, "%llu\n", ad->field); \
|
|
+} \
|
|
+static ssize_t adios_##field##_store( \
|
|
+ struct elevator_queue *e, const char *page, size_t count) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ unsigned long val; \
|
|
+ int ret; \
|
|
+ ret = kstrtoul(page, 10, &val); \
|
|
+ if (ret || val < (min_val) || val > (max_val)) \
|
|
+ return -EINVAL; \
|
|
+ ad->field = val; \
|
|
+ return count; \
|
|
+}
|
|
+
|
|
+SYSFS_ULL_DECL(global_latency_window, 0, ULLONG_MAX)
|
|
+SYSFS_ULL_DECL(compliance_flags, 0, ULLONG_MAX)
|
|
+
|
|
+#define SYSFS_INT_DECL(field, min_val, max_val) \
|
|
+static ssize_t adios_##field##_show( \
|
|
+ struct elevator_queue *e, char *page) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ return sprintf(page, "%d\n", ad->field); \
|
|
+} \
|
|
+static ssize_t adios_##field##_store( \
|
|
+ struct elevator_queue *e, const char *page, size_t count) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ int val; \
|
|
+ int ret; \
|
|
+ ret = kstrtoint(page, 10, &val); \
|
|
+ if (ret || val < (min_val) || val > (max_val)) \
|
|
+ return -EINVAL; \
|
|
+ ad->field = val; \
|
|
+ return count; \
|
|
+}
|
|
+
|
|
+SYSFS_INT_DECL(bq_refill_below_ratio, 0, 100)
|
|
+SYSFS_INT_DECL(lat_model_latency_limit, 0, 2*NSEC_PER_SEC)
|
|
+SYSFS_INT_DECL(batch_order, ADIOS_BO_OPTYPE, !!ad->is_rotational)
|
|
+
|
|
+// Show the read priority
|
|
+static ssize_t adios_read_priority_show(
|
|
+ struct elevator_queue *e, char *page) {
|
|
+ struct adios_data *ad = e->elevator_data;
|
|
+ return sprintf(page, "%d\n", ad->dl_prio[0]);
|
|
+}
|
|
+
|
|
+// Set the read priority
|
|
+static ssize_t adios_read_priority_store(
|
|
+ struct elevator_queue *e, const char *page, size_t count) {
|
|
+ struct adios_data *ad = e->elevator_data;
|
|
+ int prio;
|
|
+ int ret;
|
|
+
|
|
+ ret = kstrtoint(page, 10, &prio);
|
|
+ if (ret || prio < -20 || prio > 19)
|
|
+ return -EINVAL;
|
|
+
|
|
+ guard(spinlock_irqsave)(&ad->lock);
|
|
+ ad->dl_prio[0] = prio;
|
|
+ ad->dl_bias = 0;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+// Reset batch queue statistics
|
|
+static ssize_t adios_reset_bq_stats_store(
|
|
+ struct elevator_queue *e, const char *page, size_t count) {
|
|
+ struct adios_data *ad = e->elevator_data;
|
|
+ unsigned long val;
|
|
+ int ret;
|
|
+
|
|
+ ret = kstrtoul(page, 10, &val);
|
|
+ if (ret || val != 1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (u8 i = 0; i < ADIOS_OPTYPES; i++)
|
|
+ ad->batch_actual_max_size[i] = 0;
|
|
+
|
|
+ ad->batch_actual_max_total = 0;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+// Reset the latency model parameters or load them from user input
|
|
+static ssize_t adios_reset_lat_model_store(
|
|
+ struct elevator_queue *e, const char *page, size_t count)
|
|
+{
|
|
+ struct adios_data *ad = e->elevator_data;
|
|
+ struct latency_model *model;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * Differentiate between two modes based on input format:
|
|
+ * 1. "1": Fully reset the model (backward compatibility).
|
|
+ * 2. "R_base R_slope W_base W_slope D_base D_slope": Load values.
|
|
+ */
|
|
+ if (!strchr(page, ' ')) {
|
|
+ // Mode 1: Full reset.
|
|
+ unsigned long val;
|
|
+
|
|
+ ret = kstrtoul(page, 10, &val);
|
|
+ if (ret || val != 1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (u8 i = 0; i < ADIOS_OPTYPES; i++) {
|
|
+ model = &ad->latency_model[i];
|
|
+ sideload_latency_model(model, 0, 0);
|
|
+ }
|
|
+ } else {
|
|
+ // Mode 2: Load initial values for all latency models.
|
|
+ u64 params[3][2]; /* 0:base, 1:slope for R, W, D */
|
|
+
|
|
+ ret = sscanf(page, "%llu %llu %llu %llu %llu %llu",
|
|
+ ¶ms[ADIOS_READ ][0], ¶ms[ADIOS_READ ][1],
|
|
+ ¶ms[ADIOS_WRITE ][0], ¶ms[ADIOS_WRITE ][1],
|
|
+ ¶ms[ADIOS_DISCARD][0], ¶ms[ADIOS_DISCARD][1]);
|
|
+
|
|
+ if (ret != 6)
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (u8 i = ADIOS_READ; i <= ADIOS_DISCARD; i++) {
|
|
+ model = &ad->latency_model[i];
|
|
+ sideload_latency_model(model, params[i][0], params[i][1]);
|
|
+ }
|
|
+ }
|
|
+ reset_buckets(ad->aggr_buckets);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+// Show the ADIOS version
|
|
+static ssize_t adios_version_show(struct elevator_queue *e, char *page) {
|
|
+ return sprintf(page, "%s\n", ADIOS_VERSION);
|
|
+}
|
|
+
|
|
+// Define sysfs attributes for dynamic thresholds
|
|
+#define SHRINK_THRESHOLD_ATTR_RW(name, model_field, min_value, max_value) \
|
|
+static ssize_t adios_shrink_##name##_store( \
|
|
+ struct elevator_queue *e, const char *page, size_t count) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ unsigned long val; \
|
|
+ int ret; \
|
|
+ ret = kstrtoul(page, 10, &val); \
|
|
+ if (ret || val < min_value || val > max_value) \
|
|
+ return -EINVAL; \
|
|
+ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { \
|
|
+ struct latency_model *model = &ad->latency_model[i]; \
|
|
+ unsigned long flags; \
|
|
+ spin_lock_irqsave(&model->update_lock, flags); \
|
|
+ model->model_field = val; \
|
|
+ spin_unlock_irqrestore(&model->update_lock, flags); \
|
|
+ } \
|
|
+ return count; \
|
|
+} \
|
|
+static ssize_t adios_shrink_##name##_show( \
|
|
+ struct elevator_queue *e, char *page) { \
|
|
+ struct adios_data *ad = e->elevator_data; \
|
|
+ u32 val = 0; \
|
|
+ unsigned long flags; \
|
|
+ struct latency_model *model = &ad->latency_model[0]; \
|
|
+ spin_lock_irqsave(&model->update_lock, flags); \
|
|
+ val = model->model_field; \
|
|
+ spin_unlock_irqrestore(&model->update_lock, flags); \
|
|
+ return sprintf(page, "%u\n", val); \
|
|
+}
|
|
+
|
|
+SHRINK_THRESHOLD_ATTR_RW(at_kreqs, lm_shrink_at_kreqs, 1, 100000)
|
|
+SHRINK_THRESHOLD_ATTR_RW(at_gbytes, lm_shrink_at_gbytes, 1, 1000)
|
|
+SHRINK_THRESHOLD_ATTR_RW(resist, lm_shrink_resist, 1, 3)
|
|
+
|
|
+// Define sysfs attributes
|
|
+#define AD_ATTR(name, show_func, store_func) \
|
|
+ __ATTR(name, 0644, show_func, store_func)
|
|
+#define AD_ATTR_RW(name) \
|
|
+ __ATTR(name, 0644, adios_##name##_show, adios_##name##_store)
|
|
+#define AD_ATTR_RO(name) \
|
|
+ __ATTR(name, 0444, adios_##name##_show, NULL)
|
|
+#define AD_ATTR_WO(name) \
|
|
+ __ATTR(name, 0200, NULL, adios_##name##_store)
|
|
+
|
|
+// Define sysfs attributes for ADIOS scheduler
|
|
+static struct elv_fs_entry adios_sched_attrs[] = {
|
|
+ AD_ATTR_RO(batch_actual_max),
|
|
+ AD_ATTR_RW(bq_refill_below_ratio),
|
|
+ AD_ATTR_RW(global_latency_window),
|
|
+ AD_ATTR_RW(lat_model_latency_limit),
|
|
+ AD_ATTR_RW(batch_order),
|
|
+ AD_ATTR_RW(compliance_flags),
|
|
+
|
|
+ AD_ATTR_RW(batch_limit_read),
|
|
+ AD_ATTR_RW(batch_limit_write),
|
|
+ AD_ATTR_RW(batch_limit_discard),
|
|
+
|
|
+ AD_ATTR_RW(lat_model_read),
|
|
+ AD_ATTR_RW(lat_model_write),
|
|
+ AD_ATTR_RW(lat_model_discard),
|
|
+
|
|
+ AD_ATTR_RW(lat_target_read),
|
|
+ AD_ATTR_RW(lat_target_write),
|
|
+ AD_ATTR_RW(lat_target_discard),
|
|
+
|
|
+ AD_ATTR_RW(shrink_at_kreqs),
|
|
+ AD_ATTR_RW(shrink_at_gbytes),
|
|
+ AD_ATTR_RW(shrink_resist),
|
|
+
|
|
+ AD_ATTR_RW(read_priority),
|
|
+
|
|
+ AD_ATTR_WO(reset_bq_stats),
|
|
+ AD_ATTR_WO(reset_lat_model),
|
|
+ AD_ATTR(adios_version, adios_version_show, NULL),
|
|
+
|
|
+ __ATTR_NULL
|
|
+};
|
|
+
|
|
+// Define the ADIOS scheduler type
|
|
+static struct elevator_type mq_adios = {
|
|
+ .ops = {
|
|
+ .next_request = elv_rb_latter_request,
|
|
+ .former_request = elv_rb_former_request,
|
|
+ .limit_depth = adios_limit_depth,
|
|
+ .depth_updated = adios_depth_updated,
|
|
+ .request_merged = adios_request_merged,
|
|
+ .requests_merged = adios_merged_requests,
|
|
+ .bio_merge = adios_bio_merge,
|
|
+ .insert_requests = adios_insert_requests,
|
|
+ .prepare_request = adios_prepare_request,
|
|
+ .dispatch_request = adios_dispatch_request,
|
|
+ .completed_request = adios_completed_request,
|
|
+ .finish_request = adios_finish_request,
|
|
+ .has_work = adios_has_work,
|
|
+ .init_sched = adios_init_sched,
|
|
+ .exit_sched = adios_exit_sched,
|
|
+ },
|
|
+ .elevator_attrs = adios_sched_attrs,
|
|
+ .elevator_name = "adios",
|
|
+ .elevator_owner = THIS_MODULE,
|
|
+};
|
|
+MODULE_ALIAS("mq-adios-iosched");
|
|
+
|
|
+#define ADIOS_PROGNAME "Adaptive Deadline I/O Scheduler"
|
|
+#define ADIOS_AUTHOR "Masahito Suzuki"
|
|
+
|
|
+// Initialize the ADIOS scheduler module
|
|
+static int __init adios_init(void) {
|
|
+ printk(KERN_INFO "%s %s by %s\n",
|
|
+ ADIOS_PROGNAME, ADIOS_VERSION, ADIOS_AUTHOR);
|
|
+ return elv_register(&mq_adios);
|
|
+}
|
|
+
|
|
+// Exit the ADIOS scheduler module
|
|
+static void __exit adios_exit(void) {
|
|
+ elv_unregister(&mq_adios);
|
|
+}
|
|
+
|
|
+module_init(adios_init);
|
|
+module_exit(adios_exit);
|
|
+
|
|
+MODULE_AUTHOR(ADIOS_AUTHOR);
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION(ADIOS_PROGNAME);
|
|
\ No newline at end of file
|
|
diff --git a/block/elevator.c b/block/elevator.c
|
|
index 2161b6eea680..72a0dc6b345f 100644
|
|
--- a/block/elevator.c
|
|
+++ b/block/elevator.c
|
|
@@ -740,6 +740,14 @@ void elevator_set_default(struct request_queue *q)
|
|
if (q->tag_set->flags & BLK_MQ_F_NO_SCHED_BY_DEFAULT)
|
|
return;
|
|
|
|
+#ifdef CONFIG_MQ_IOSCHED_DEFAULT_ADIOS
|
|
+ ctx.name = "adios";
|
|
+#else // !CONFIG_MQ_IOSCHED_DEFAULT_ADIOS
|
|
+ bool is_sq = q->nr_hw_queues == 1 || blk_mq_is_shared_tags(q->tag_set->flags);
|
|
+ if (!is_sq)
|
|
+ return;
|
|
+#endif // CONFIG_MQ_IOSCHED_DEFAULT_ADIOS
|
|
+
|
|
/*
|
|
* For single queue devices, default to using mq-deadline. If we
|
|
* have multiple queues or mq-deadline is not available, default
|
|
@@ -749,13 +757,10 @@ void elevator_set_default(struct request_queue *q)
|
|
if (!ctx.type)
|
|
return;
|
|
|
|
- if ((q->nr_hw_queues == 1 ||
|
|
- blk_mq_is_shared_tags(q->tag_set->flags))) {
|
|
- err = elevator_change(q, &ctx);
|
|
- if (err < 0)
|
|
- pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n",
|
|
- ctx.name, err);
|
|
- }
|
|
+ err = elevator_change(q, &ctx);
|
|
+ if (err < 0)
|
|
+ pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n",
|
|
+ ctx.name, err);
|
|
elevator_put(ctx.type);
|
|
}
|
|
|