aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/subr_disk.c
blob: c8ed9ced60726cd769a1ff05fb7960c7d700ea7f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*-
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include "opt_geom.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bio.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <geom/geom_disk.h>

/*-
 * Disk error is the preface to plaintive error messages
 * about failing disk transfers.  It prints messages of the form
 * 	"hp0g: BLABLABLA cmd=read fsbn 12345 of 12344-12347"
 * blkdone should be -1 if the position of the error is unknown.
 * The message is printed with printf.
 */
void
disk_err(struct bio *bp, const char *what, int blkdone, int nl)
{
	daddr_t sn;

	if (bp->bio_dev != NULL)
		printf("%s: %s ", devtoname(bp->bio_dev), what);
	else if (bp->bio_disk != NULL)
		printf("%s%d: %s ",
		    bp->bio_disk->d_name, bp->bio_disk->d_unit, what);
	else
		printf("disk??: %s ", what);
	switch(bp->bio_cmd) {
	case BIO_READ:		printf("cmd=read "); break;
	case BIO_WRITE:		printf("cmd=write "); break;
	case BIO_DELETE:	printf("cmd=delete "); break;
	case BIO_GETATTR:	printf("cmd=getattr "); break;
	case BIO_FLUSH:		printf("cmd=flush "); break;
	default:		printf("cmd=%x ", bp->bio_cmd); break;
	}
	sn = bp->bio_pblkno;
	if (bp->bio_bcount <= DEV_BSIZE) {
		printf("fsbn %jd%s", (intmax_t)sn, nl ? "\n" : "");
		return;
	}
	if (blkdone >= 0) {
		sn += blkdone;
		printf("fsbn %jd of ", (intmax_t)sn);
	}
	printf("%jd-%jd", (intmax_t)bp->bio_pblkno,
	    (intmax_t)(bp->bio_pblkno + (bp->bio_bcount - 1) / DEV_BSIZE));
	if (nl)
		printf("\n");
}

/*
 * BIO queue implementation
 */

void
bioq_init(struct bio_queue_head *head)
{
	TAILQ_INIT(&head->queue);
	head->last_offset = 0;
	head->insert_point = NULL;
}

void
bioq_remove(struct bio_queue_head *head, struct bio *bp)
{
	if (bp == head->insert_point) {
		head->last_offset = bp->bio_offset;
		head->insert_point = TAILQ_NEXT(bp, bio_queue);
		if (head->insert_point == NULL) {
			head->last_offset = 0;
			head->insert_point = TAILQ_FIRST(&head->queue);
		}
	}
	TAILQ_REMOVE(&head->queue, bp, bio_queue);
}

void
bioq_flush(struct bio_queue_head *head, struct devstat *stp, int error)
{
	struct bio *bp;

	while ((bp = bioq_takefirst(head)) != NULL)
		biofinish(bp, stp, error);
}

void
bioq_insert_head(struct bio_queue_head *head, struct bio *bp)
{

	if (TAILQ_EMPTY(&head->queue))
		head->insert_point = bp;
	TAILQ_INSERT_HEAD(&head->queue, bp, bio_queue);
}

void
bioq_insert_tail(struct bio_queue_head *head, struct bio *bp)
{

	if (TAILQ_EMPTY(&head->queue))
		head->insert_point = bp;
	TAILQ_INSERT_TAIL(&head->queue, bp, bio_queue);
}

struct bio *
bioq_first(struct bio_queue_head *head)
{

	return (TAILQ_FIRST(&head->queue));
}

struct bio *
bioq_takefirst(struct bio_queue_head *head)
{
	struct bio *bp;

	bp = TAILQ_FIRST(&head->queue);
	if (bp != NULL)
		bioq_remove(head, bp);
	return (bp);
}

/*
 * Seek sort for disks.
 *
 * The disksort algorithm sorts all requests in a single queue while keeping
 * track of the current position of the disk with insert_point and
 * last_offset.  last_offset is the offset of the last block sent to disk, or
 * 0 once we reach the end.  insert_point points to the first buf after
 * last_offset, and is used to slightly speed up insertions.  Blocks are
 * always sorted in ascending order and the queue always restarts at 0.
 * This implements the one-way scan which optimizes disk seek times.
 */
void
bioq_disksort(bioq, bp)
	struct bio_queue_head *bioq;
	struct bio *bp;
{
	struct bio *bq;
	struct bio *bn;

	/*
	 * If the queue is empty then it's easy.
	 */
	if (bioq_first(bioq) == NULL) {
		bioq_insert_tail(bioq, bp);
		return;
	}
	/*
	 * Optimize for sequential I/O by seeing if we go at the tail.
	 */
	bq = TAILQ_LAST(&bioq->queue, bio_queue);
	if (bp->bio_offset > bq->bio_offset) {
		TAILQ_INSERT_AFTER(&bioq->queue, bq, bp, bio_queue);
		return;
	}
	/*
	 * Pick our scan start based on the last request.  A poor man's
	 * binary search.
	 */
	if (bp->bio_offset >= bioq->last_offset) { 
		bq = bioq->insert_point;
		/*
		 * If we're before the next bio and after the last offset,
		 * update insert_point;
		 */
		if (bp->bio_offset < bq->bio_offset) {
			bioq->insert_point = bp;
			TAILQ_INSERT_BEFORE(bq, bp, bio_queue);
			return;
		}
	} else
		bq = TAILQ_FIRST(&bioq->queue);
	if (bp->bio_offset < bq->bio_offset) {
		TAILQ_INSERT_BEFORE(bq, bp, bio_queue);
		return;
	}
	/* Insertion sort */
	while ((bn = TAILQ_NEXT(bq, bio_queue)) != NULL) {
		if (bp->bio_offset < bn->bio_offset)
			break;
		bq = bn;
	}
	TAILQ_INSERT_AFTER(&bioq->queue, bq, bp, bio_queue);
}