blob: 549891d76ef5dba2728d905ff75217b95cf26f70 (
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
|
/* cg6.c: CGSIX (GX, GXplus, TGX) frame buffer driver
*
* Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net)
* Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz)
* Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx)
* Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be)
*
* Driver layout based loosely on tgafb.c, see that file for credits.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/of_device.h>
#include <asm/fbio.h>
#include "sbuslib.h"
/*
* Local functions.
*/
static int cg6_setcolreg(unsigned, unsigned, unsigned, unsigned,
unsigned, struct fb_info *);
static int cg6_blank(int, struct fb_info *);
static void cg6_imageblit(struct fb_info *, const struct fb_image *);
static void cg6_fillrect(struct fb_info *, const struct fb_fillrect *);
static int cg6_sync(struct fb_info *);
static int cg6_mmap(struct fb_info *, struct vm_area_struct *);
static int cg6_ioctl(struct fb_info *, unsigned int, unsigned long);
static void cg6_copyarea(struct fb_info *info, const struct fb_copyarea *area);
/*
* Frame buffer operations
*/
static struct fb_ops cg6_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = cg6_setcolreg,
.fb_blank = cg6_blank,
.fb_fillrect = cg6_fillrect,
.fb_copyarea = cg6_copyarea,
.fb_imageblit = cg6_imageblit,
.fb_sync = cg6_sync,
.fb_mmap = cg6_mmap,
.fb_ioctl = cg6_ioctl,
#ifdef CONFIG_COMPAT
.fb_compat_ioctl = sbusfb_compat_ioctl,
#endif
};
/* Offset of interesting structures in the OBIO space */
/*
* Brooktree is the video dac and is funny to program on the cg6.
* (it's even funnier on the cg3)
* The FBC could be the frame buffer control
* The FHC could is the frame buffer hardware control.
*/
#define CG6_ROM_OFFSET 0x0UL
#define CG6_BROOKTREE_OFFSET 0x200000UL
#define CG6_DHC_OFFSET 0x240000UL
#define CG6_ALT_OFFSET 0x280000UL
#define CG6_FHC_OFFSET 0x300000UL
#define CG6_THC_OFFSET 0x301000UL
#define CG6_FBC_OFFSET 0x700000UL
#define CG6_TEC_OFFSET 0x701000UL
#define CG6_RAM_OFFSET 0x800000UL
/* FHC definitions */
#define CG6_FHC_FBID_SHIFT 24
#define CG6_FHC_FBID_MASK 255
#define CG6_FHC_REV_SHIFT 20
#define CG6_FHC_REV_MASK 15
#define CG6_FHC_FROP_DISABLE (1 << 19)
#define CG6_FHC_ROW_DISABLE (1 << 18)
#define CG6_FHC_SRC_DISABLE (1 << 17)
#define CG6_FHC_DST_DISABLE (1 << 16)
#define CG6_FHC_RESET (1 << 15)
#define CG6_FHC_LITTLE_ENDIAN (1 << 13)
#define CG6_FHC_RES_MASK (3 << 11)
#define CG6_FHC_1024 (0 << 11)
#define CG6_FHC_1152 (1 << 11)
#define CG6_FHC_1280 (2 << 11)
#define CG6_FHC_1600 (3 << 11)
#define CG6_FHC_CPU_MASK (3 << 9)
#define CG6_FHC_CPU_SPARC (0 << 9)
#define CG6_FHC_CPU_68020 (1 << 9)
#define CG6_FHC_CPU_386 (2 << 9)
#define CG6_FHC_TEST (1 << 8)
#define CG6_FHC_TEST_X_SHIFT 4
#define CG6_FHC_TEST_X_MASK 15
#define CG6_FHC_TEST_Y_SHIFT 0
#define CG6_FHC_TEST_Y_MASK 15
/* FBC mode definitions */
#define CG6_FBC_BLIT_IGNORE 0x00000000
#define CG6_FBC_BLIT_NOSRC 0x00100000
#define CG6_FBC_BLIT_SRC 0x00200000
#define CG6_FBC_BLIT_ILLEGAL 0x00300000
#define CG6_FBC_BLIT_MASK 0x00300000
#define CG6_FBC_VBLANK 0x00080000
#define CG6_FBC_MODE_IGNORE 0x00000000
#define CG6_FBC_MODE_COLOR8 0x00020000
#define CG6_FBC_MODE_COLOR1 0x00040000
#define CG6_FBC_MODE_HRMONO 0x00060000
#define CG6_FBC_MODE_MASK 0x00060000
#define CG6_FBC_DRAW_IGNORE 0x00000000
#define CG6_FBC_DRAW_RENDER 0x00008000
#define CG6_FBC_DRAW_PICK 0x00010000
#define CG6_FBC_DRAW_ILLEGAL 0x00018000
#define CG6_FBC_DRAW_MASK 0x00018000
#define CG6_FBC_BWRITE0_IGNORE 0x00000000
#define CG6_FBC_BWRITE0_ENABLE 0x00002000
#define CG6_FBC_BWRITE0_DISABLE 0x00004000
#define CG6_FBC_BWRITE0_ILLEGAL 0x00006000
#define CG6_FBC_BWRITE0_MASK 0x00006000
#define CG6_FBC_BWRITE1_IGNORE 0x00000000
#define CG6_FBC_BWRITE1_ENABLE 0x00000800
#define CG6_FBC_BWRITE1_DISABLE 0x00001000
#define CG6_FBC_BWRITE1_ILLEGAL 0x00001800
#define CG6_FBC_BWRITE1_MASK 0x00001800
#define CG6_FBC_BREAD_IGNORE 0x00000000
#define CG6_FBC_BREAD_0 0x00000200
#define CG6_FBC_BREAD_1 0x00000400
#define CG6_FBC_BREAD_ILLEGAL 0x00000600
#define CG6_FBC_BREAD_MASK 0x00000600
#define CG6_FBC_BDISP_IGNORE 0x00000000
#define CG6_FBC_BDISP_0 0x00000080
#define CG6_FBC_BDISP_1 0x00000100
#define CG6_FBC_BDISP_ILLEGAL 0x00000180
#define CG6_FBC_BDISP_MASK 0x00000180
#define CG6_FBC_INDEX_MOD 0x00000040
#define CG6_FBC_INDEX_MASK 0x00000030
/* THC definitions */
#define CG6_THC_MISC_REV_SHIFT 16
#define CG6_THC_MISC_REV_MASK 15
#define CG6_THC_MISC_RESET (1 << 12)
#define CG6_THC_MISC_VIDEO (1 << 10)
#define CG6_THC_MISC_SYNC (1 << 9)
#define CG6_THC_MISC_VSYNC (1 << 8)
#define CG6_THC_MISC_SYNC_ENAB (1 << 7)
#define CG6_THC_MISC_CURS_RES (1 << 6)
#define CG6_THC_MISC_INT_ENAB (1 << 5)
#define CG6_THC_MISC_INT (1 << 4)
#define CG6_THC_MISC_INIT 0x9f
/* The contents are unknown */
struct cg6_tec {
int tec_matrix;
int tec_clip;
int tec_vdc;
};
struct cg6_thc {
u32 thc_pad0[512];
u32 thc_hs; /* hsync timing */
u32 thc_hsdvs;
u32 thc_hd;
u32 thc_vs; /* vsync timing */
u32 thc_vd;
u32 thc_refresh;
u32 thc_misc;
u32 thc_pad1[56];
u32 thc_cursxy; /* cursor x,y position (16 bits each) */
u32 thc_cursmask[32]; /* cursor mask bits */
u32 thc_cursbits[32]; /* what to show where mask enabled */
};
struct cg6_fbc {
u32 xxx0[1];
u32 mode;
u32 clip;
u32 xxx1[1];
u32 s;
u32 draw;
u32 blit;
u32 font;
u32 xxx2[24];
u32 x0, y0, z0, color0;
u32 x1, y1, z1, color1;
u32 x2, y2, z2, color2;
u32 x3, y3, z3, color3;
u32 offx, offy;
u32 xxx3[2];
u32 incx, incy;
u32 xxx4[2];
u32 clipminx, clipminy;
u32 xxx5[2];
u32 clipmaxx, clipmaxy;
u32 xxx6[2];
u32 fg;
u32 bg;
u32 alu;
u32 pm;
u32 pixelm;
u32 xxx7[2];
u32 patalign;
u32 pattern[8];
u32 xxx8[432];
u32 apointx, apointy, apointz;
u32 xxx9[1];
u32 rpointx, rpointy, rpointz;
u32 xxx10[5];
u32 pointr, pointg, pointb, pointa;
u32 alinex, aliney, alinez;
u32 xxx11[1];
u32 rlinex, rliney, rlinez;
u32 xxx12[5];
u32 liner, lineg, lineb, linea;
u32 atrix, atriy, atriz;
u32 xxx13[1];
u32 rtrix, rtriy, rtriz;
u32 xxx14[5];
u32 trir, trig, trib, tria;
u32 aquadx, aquady, aquadz;
u32 xxx15[1];
u32 rquadx, rquady, rquadz;
u32 xxx16[5];
u32 quadr, quadg, quadb, quada;
u32 arectx, arecty, arectz;
u32 xxx17[1];
u32 rrectx, rrecty, rrectz;
u32 xxx18[5];
u32 rectr, rectg, rectb, recta;
};
struct bt_regs {
u32 addr;
u32 color_map;
u32 control;
u32 cursor;
};
struct cg6_par {
spinlock_t lock;
struct bt_regs __iomem *bt;
struct cg6_fbc __iomem *fbc;
struct cg6_thc __iomem *thc;
struct cg6_tec __iomem *tec;
u32 __iomem *fhc;
u32 flags;
#define CG6_FLAG_BLANKED 0x00000001
unsigned long physbase;
unsigned long which_io;
unsigned long fbsize;
};
static int cg6_sync(struct fb_info *info)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct cg6_fbc __iomem *fbc = par->fbc;
int limit = 10000;
do {
if (!(sbus_readl(&fbc->s) & 0x10000000))
break;
udelay(10);
} while (--limit > 0);
return 0;
}
/**
* cg6_fillrect - Draws a rectangle on the screen.
*
* @info: frame buffer structure that represents a single frame buffer
* @rect: structure defining the rectagle and operation.
*/
static void cg6_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct cg6_fbc __iomem *fbc = par->fbc;
unsigned long flags;
s32 val;
/* CG6 doesn't handle ROP_XOR */
spin_lock_irqsave(&par->lock, flags);
cg6_sync(info);
sbus_writel(rect->color, &fbc->fg);
sbus_writel(~(u32)0, &fbc->pixelm);
sbus_writel(0xea80ff00, &fbc->alu);
sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip);
sbus_writel(~(u32)0, &fbc->pm);
sbus_writel(rect->dy, &fbc->arecty);
sbus_writel(rect->dx, &fbc->arectx);
sbus_writel(rect->dy + rect->height, &fbc->arecty);
sbus_writel(rect->dx + rect->width, &fbc->arectx);
do {
val = sbus_readl(&fbc->draw);
} while (val < 0 && (val & 0x20000000));
spin_unlock_irqrestore(&par->lock, flags);
}
/**
* cg6_copyarea - Copies one area of the screen to another area.
*
* @info: frame buffer structure that represents a single frame buffer
* @area: Structure providing the data to copy the framebuffer contents
* from one region to another.
*
* This drawing operation copies a rectangular area from one area of the
* screen to another area.
*/
static void cg6_copyarea(struct fb_info *info, const struct fb_copyarea *area)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct cg6_fbc __iomem *fbc = par->fbc;
unsigned long flags;
int i;
spin_lock_irqsave(&par->lock, flags);
cg6_sync(info);
sbus_writel(0xff, &fbc->fg);
sbus_writel(0x00, &fbc->bg);
sbus_writel(~0, &fbc->pixelm);
sbus_writel(0xe880cccc, &fbc->alu);
sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip);
sbus_writel(area->sy, &fbc->y0);
sbus_writel(area->sx, &fbc->x0);
sbus_writel(area->sy + area->height - 1, &fbc->y1);
sbus_writel(area->sx + area->width - 1, &fbc->x1);
sbus_writel(area->dy, &fbc->y2);
sbus_writel(area->dx, &fbc->x2);
sbus_writel(area->dy + area->height - 1, &fbc->y3);
sbus_writel(area->dx + area->width - 1, &fbc->x3);
do {
i = sbus_readl(&fbc->blit);
} while (i < 0 && (i & 0x20000000));
spin_unlock_irqrestore(&par->lock, flags);
}
/**
* cg6_imageblit - Copies a image from system memory to the screen.
*
* @info: frame buffer structure that represents a single frame buffer
* @image: structure defining the image.
*/
static void cg6_imageblit(struct fb_info *info, const struct fb_image *image)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct cg6_fbc __iomem *fbc = par->fbc;
const u8 *data = image->data;
unsigned long flags;
u32 x, y;
int i, width;
if (image->depth > 1) {
cfb_imageblit(info, image);
return;
}
spin_lock_irqsave(&par->lock, flags);
cg6_sync(info);
sbus_writel(image->fg_color, &fbc->fg);
sbus_writel(image->bg_color, &fbc->bg);
sbus_writel(0x140000, &fbc->mode);
sbus_writel(0xe880fc30, &fbc->alu);
sbus_writel(~(u32)0, &fbc->pixelm);
sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip);
sbus_writel(0xff, &fbc->pm);
sbus_writel(32, &fbc->incx);
sbus_writel(0, &fbc->incy);
x = image->dx;
y = image->dy;
for (i = 0; i < image->height; i++) {
width = image->width;
while (width >= 32) {
u32 val;
sbus_writel(y, &fbc->y0);
sbus_writel(x, &fbc->x0);
sbus_writel(x + 32 - 1, &fbc->x1);
val = ((u32)data[0] << 24) |
((u32)data[1] << 16) |
((u32)data[2] << 8) |
((u32)data[3] << 0);
sbus_writel(val, &fbc->font);
data += 4;
x += 32;
width -= 32;
}
if (width) {
u32 val;
sbus_writel(y, &fbc->y0);
sbus_writel(x, &fbc->x0);
sbus_writel(x + width - 1, &fbc->x1);
if (width <= 8) {
val = (u32) data[0] << 24;
data += 1;
} else if (width <= 16) {
val = ((u32) data[0] << 24) |
((u32) data[1] << 16);
data += 2;
} else {
val = ((u32) data[0] << 24) |
((u32) data[1] << 16) |
((u32) data[2] << 8);
data += 3;
}
sbus_writel(val, &fbc->font);
}
y += 1;
x = image->dx;
}
spin_unlock_irqrestore(&par->lock, flags);
}
/**
* cg6_setcolreg - Sets a color register.
*
* @regno: boolean, 0 copy local, 1 get_user() function
* @red: frame buffer colormap structure
* @green: The green value which can be up to 16 bits wide
* @blue: The blue value which can be up to 16 bits wide.
* @transp: If supported the alpha value which can be up to 16 bits wide.
* @info: frame buffer info structure
*/
static int cg6_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct bt_regs __iomem *bt = par->bt;
unsigned long flags;
if (regno >= 256)
return 1;
red >>= 8;
green >>= 8;
blue >>= 8;
spin_lock_irqsave(&par->lock, flags);
sbus_writel((u32)regno << 24, &bt->addr);
sbus_writel((u32)red << 24, &bt->color_map);
sbus_writel((u32)green << 24, &bt->color_map);
sbus_writel((u32)blue << 24, &bt->color_map);
spin_unlock_irqrestore(&par->lock, flags);
return 0;
}
/**
* cg6_blank - Blanks the display.
*
* @blank_mode: the blank mode we want.
* @info: frame buffer structure that represents a single frame buffer
*/
static int cg6_blank(int blank, struct fb_info *info)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct cg6_thc __iomem *thc = par->thc;
unsigned long flags;
u32 val;
spin_lock_irqsave(&par->lock, flags);
val = sbus_readl(&thc->thc_misc);
switch (blank) {
case FB_BLANK_UNBLANK: /* Unblanking */
val |= CG6_THC_MISC_VIDEO;
par->flags &= ~CG6_FLAG_BLANKED;
break;
case FB_BLANK_NORMAL: /* Normal blanking */
case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */
case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */
case FB_BLANK_POWERDOWN: /* Poweroff */
val &= ~CG6_THC_MISC_VIDEO;
par->flags |= CG6_FLAG_BLANKED;
break;
}
sbus_writel(val, &thc->thc_misc);
spin_unlock_irqrestore(&par->lock, flags);
return 0;
}
static struct sbus_mmap_map cg6_mmap_map[] = {
{
.voff = CG6_FBC,
.poff = CG6_FBC_OFFSET,
.size = PAGE_SIZE
},
{
.voff = CG6_TEC,
.poff = CG6_TEC_OFFSET,
.size = PAGE_SIZE
},
{
.voff = CG6_BTREGS,
.poff = CG6_BROOKTREE_OFFSET,
.size = PAGE_SIZE
},
{
.voff = CG6_FHC,
.poff = CG6_FHC_OFFSET,
.size = PAGE_SIZE
},
{
.voff = CG6_THC,
.poff = CG6_THC_OFFSET,
.size = PAGE_SIZE
},
{
.voff = CG6_ROM,
.poff = CG6_ROM_OFFSET,
.size = 0x10000
},
{
.voff = CG6_RAM,
.poff = CG6_RAM_OFFSET,
.size = SBUS_MMAP_FBSIZE(1)
},
{
.voff = CG6_DHC,
.poff = CG6_DHC_OFFSET,
.size = 0x40000
},
{ .size = 0 }
};
static int cg6_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct cg6_par *par = (struct cg6_par *)info->par;
return sbusfb_mmap_helper(cg6_mmap_map,
par->physbase, par->fbsize,
par->which_io, vma);
}
static int cg6_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
struct cg6_par *par = (struct cg6_par *)info->par;
return sbusfb_ioctl_helper(cmd, arg, info,
FBTYPE_SUNFAST_COLOR, 8, par->fbsize);
}
/*
* Initialisation
*/
static void __devinit cg6_init_fix(struct fb_info *info, int linebytes)
{
struct cg6_par *par = (struct cg6_par *)info->par;
const char *cg6_cpu_name, *cg6_card_name;
u32 conf;
conf = sbus_readl(par->fhc);
switch (conf & CG6_FHC_CPU_MASK) {
case CG6_FHC_CPU_SPARC:
cg6_cpu_name = "sparc";
break;
case CG6_FHC_CPU_68020:
cg6_cpu_name = "68020";
break;
default:
cg6_cpu_name = "i386";
break;
};
if (((conf >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK) >= 11) {
if (par->fbsize <= 0x100000)
cg6_card_name = "TGX";
else
cg6_card_name = "TGX+";
} else {
if (par->fbsize <= 0x100000)
cg6_card_name = "GX";
else
cg6_card_name = "GX+";
}
sprintf(info->fix.id, "%s %s", cg6_card_name, cg6_cpu_name);
info->fix.id[sizeof(info->fix.id) - 1] = 0;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
info->fix.line_length = linebytes;
info->fix.accel = FB_ACCEL_SUN_CGSIX;
}
/* Initialize Brooktree DAC */
static void __devinit cg6_bt_init(struct cg6_par *par)
{
struct bt_regs __iomem *bt = par->bt;
sbus_writel(0x04 << 24, &bt->addr); /* color planes */
sbus_writel(0xff << 24, &bt->control);
sbus_writel(0x05 << 24, &bt->addr);
sbus_writel(0x00 << 24, &bt->control);
sbus_writel(0x06 << 24, &bt->addr); /* overlay plane */
sbus_writel(0x73 << 24, &bt->control);
sbus_writel(0x07 << 24, &bt->addr);
sbus_writel(0x00 << 24, &bt->control);
}
static void __devinit cg6_chip_init(struct fb_info *info)
{
struct cg6_par *par = (struct cg6_par *)info->par;
struct cg6_tec __iomem *tec = par->tec;
struct cg6_fbc __iomem *fbc = par->fbc;
u32 rev, conf, mode;
int i;
/* Turn off stuff in the Transform Engine. */
sbus_writel(0, &tec->tec_matrix);
sbus_writel(0, &tec->tec_clip);
sbus_writel(0, &tec->tec_vdc);
/* Take care of bugs in old revisions. */
rev = (sbus_readl(par->fhc) >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK;
if (rev < 5) {
conf = (sbus_readl(par->fhc) & CG6_FHC_RES_MASK) |
CG6_FHC_CPU_68020 | CG6_FHC_TEST |
(11 << CG6_FHC_TEST_X_SHIFT) |
(11 << CG6_FHC_TEST_Y_SHIFT);
if (rev < 2)
conf |= CG6_FHC_DST_DISABLE;
sbus_writel(conf, par->fhc);
}
/* Set things in the FBC. Bad things appear to happen if we do
* back to back store/loads on the mode register, so copy it
* out instead. */
mode = sbus_readl(&fbc->mode);
do {
i = sbus_readl(&fbc->s);
} while (i & 0x10000000);
mode &= ~(CG6_FBC_BLIT_MASK | CG6_FBC_MODE_MASK |
CG6_FBC_DRAW_MASK | CG6_FBC_BWRITE0_MASK |
CG6_FBC_BWRITE1_MASK | CG6_FBC_BREAD_MASK |
CG6_FBC_BDISP_MASK);
mode |= (CG6_FBC_BLIT_SRC | CG6_FBC_MODE_COLOR8 |
CG6_FBC_DRAW_RENDER | CG6_FBC_BWRITE0_ENABLE |
CG6_FBC_BWRITE1_DISABLE | CG6_FBC_BREAD_0 |
CG6_FBC_BDISP_0);
sbus_writel(mode, &fbc->mode);
sbus_writel(0, &fbc->clip);
sbus_writel(0, &fbc->offx);
sbus_writel(0, &fbc->offy);
sbus_writel(0, &fbc->clipminx);
sbus_writel(0, &fbc->clipminy);
sbus_writel(info->var.xres - 1, &fbc->clipmaxx);
sbus_writel(info->var.yres - 1, &fbc->clipmaxy);
}
static void cg6_unmap_regs(struct of_device *op, struct fb_info *info,
struct cg6_par *par)
{
if (par->fbc)
of_iounmap(&op->resource[0], par->fbc, 4096);
if (par->tec)
of_iounmap(&op->resource[0], par->tec, sizeof(struct cg6_tec));
if (par->thc)
of_iounmap(&op->resource[0], par->thc, sizeof(struct cg6_thc));
if (par->bt)
of_iounmap(&op->resource[0], par->bt, sizeof(struct bt_regs));
if (par->fhc)
of_iounmap(&op->resource[0], par->fhc, sizeof(u32));
if (info->screen_base)
of_iounmap(&op->resource[0], info->screen_base, par->fbsize);
}
static int __devinit cg6_probe(struct of_device *op,
const struct of_device_id *match)
{
struct device_node *dp = op->node;
struct fb_info *info;
struct cg6_par *par;
int linebytes, err;
int dblbuf;
info = framebuffer_alloc(sizeof(struct cg6_par), &op->dev);
err = -ENOMEM;
if (!info)
goto out_err;
par = info->par;
spin_lock_init(&par->lock);
par->physbase = op->resource[0].start;
par->which_io = op->resource[0].flags & IORESOURCE_BITS;
sbusfb_fill_var(&info->var, dp->node, 8);
info->var.red.length = 8;
info->var.green.length = 8;
info->var.blue.length = 8;
linebytes = of_getintprop_default(dp, "linebytes",
info->var.xres);
par->fbsize = PAGE_ALIGN(linebytes * info->var.yres);
dblbuf = of_getintprop_default(dp, "dblbuf", 0);
if (dblbuf)
par->fbsize *= 4;
par->fbc = of_ioremap(&op->resource[0], CG6_FBC_OFFSET,
4096, "cgsix fbc");
par->tec = of_ioremap(&op->resource[0], CG6_TEC_OFFSET,
sizeof(struct cg6_tec), "cgsix tec");
par->thc = of_ioremap(&op->resource[0], CG6_THC_OFFSET,
sizeof(struct cg6_thc), "cgsix thc");
par->bt = of_ioremap(&op->resource[0], CG6_BROOKTREE_OFFSET,
sizeof(struct bt_regs), "cgsix dac");
par->fhc = of_ioremap(&op->resource[0], CG6_FHC_OFFSET,
sizeof(u32), "cgsix fhc");
info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_IMAGEBLIT |
FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT |
FBINFO_READS_FAST;
info->fbops = &cg6_ops;
info->screen_base = of_ioremap(&op->resource[0], CG6_RAM_OFFSET,
par->fbsize, "cgsix ram");
if (!par->fbc || !par->tec || !par->thc ||
!par->bt || !par->fhc || !info->screen_base)
goto out_unmap_regs;
info->var.accel_flags = FB_ACCELF_TEXT;
cg6_bt_init(par);
cg6_chip_init(info);
cg6_blank(0, info);
if (fb_alloc_cmap(&info->cmap, 256, 0))
goto out_unmap_regs;
fb_set_cmap(&info->cmap, info);
cg6_init_fix(info, linebytes);
err = register_framebuffer(info);
if (err < 0)
goto out_dealloc_cmap;
dev_set_drvdata(&op->dev, info);
printk("%s: CGsix [%s] at %lx:%lx\n",
dp->full_name, info->fix.id,
par->which_io, par->physbase);
return 0;
out_dealloc_cmap:
fb_dealloc_cmap(&info->cmap);
out_unmap_regs:
cg6_unmap_regs(op, info, par);
out_err:
return err;
}
static int __devexit cg6_remove(struct of_device *op)
{
struct fb_info *info = dev_get_drvdata(&op->dev);
struct cg6_par *par = info->par;
unregister_framebuffer(info);
fb_dealloc_cmap(&info->cmap);
cg6_unmap_regs(op, info, par);
framebuffer_release(info);
dev_set_drvdata(&op->dev, NULL);
return 0;
}
static struct of_device_id cg6_match[] = {
{
.name = "cgsix",
},
{
.name = "cgthree+",
},
{},
};
MODULE_DEVICE_TABLE(of, cg6_match);
static struct of_platform_driver cg6_driver = {
.name = "cg6",
.match_table = cg6_match,
.probe = cg6_probe,
.remove = __devexit_p(cg6_remove),
};
static int __init cg6_init(void)
{
if (fb_get_options("cg6fb", NULL))
return -ENODEV;
return of_register_driver(&cg6_driver, &of_bus_type);
}
static void __exit cg6_exit(void)
{
of_unregister_driver(&cg6_driver);
}
module_init(cg6_init);
module_exit(cg6_exit);
MODULE_DESCRIPTION("framebuffer driver for CGsix chipsets");
MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");
MODULE_VERSION("2.0");
MODULE_LICENSE("GPL");
|
3322' href='#n3322'>3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
/*
* CFQ, or complete fairness queueing, disk scheduler.
*
* Based on ideas from a previously unfinished io
* scheduler (round robin per-process disk scheduling) and Andrea Arcangeli.
*
* Copyright (C) 2003 Jens Axboe <axboe@kernel.dk>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/blkdev.h>
#include <linux/elevator.h>
#include <linux/jiffies.h>
#include <linux/rbtree.h>
#include <linux/ioprio.h>
#include <linux/blktrace_api.h>
#include "cfq.h"
/*
* tunables
*/
/* max queue in one round of service */
static const int cfq_quantum = 8;
static const int cfq_fifo_expire[2] = { HZ / 4, HZ / 8 };
/* maximum backwards seek, in KiB */
static const int cfq_back_max = 16 * 1024;
/* penalty of a backwards seek */
static const int cfq_back_penalty = 2;
static const int cfq_slice_sync = HZ / 10;
static int cfq_slice_async = HZ / 25;
static const int cfq_slice_async_rq = 2;
static int cfq_slice_idle = HZ / 125;
static int cfq_group_idle = HZ / 125;
static const int cfq_target_latency = HZ * 3/10; /* 300 ms */
static const int cfq_hist_divisor = 4;
/*
* offset from end of service tree
*/
#define CFQ_IDLE_DELAY (HZ / 5)
/*
* below this threshold, we consider thinktime immediate
*/
#define CFQ_MIN_TT (2)
#define CFQ_SLICE_SCALE (5)
#define CFQ_HW_QUEUE_MIN (5)
#define CFQ_SERVICE_SHIFT 12
#define CFQQ_SEEK_THR (sector_t)(8 * 100)
#define CFQQ_CLOSE_THR (sector_t)(8 * 1024)
#define CFQQ_SECT_THR_NONROT (sector_t)(2 * 32)
#define CFQQ_SEEKY(cfqq) (hweight32(cfqq->seek_history) > 32/8)
#define RQ_CIC(rq) \
((struct cfq_io_context *) (rq)->elevator_private[0])
#define RQ_CFQQ(rq) (struct cfq_queue *) ((rq)->elevator_private[1])
#define RQ_CFQG(rq) (struct cfq_group *) ((rq)->elevator_private[2])
static struct kmem_cache *cfq_pool;
static struct kmem_cache *cfq_ioc_pool;
static DEFINE_PER_CPU(unsigned long, cfq_ioc_count);
static struct completion *ioc_gone;
static DEFINE_SPINLOCK(ioc_gone_lock);
static DEFINE_SPINLOCK(cic_index_lock);
static DEFINE_IDA(cic_index_ida);
#define CFQ_PRIO_LISTS IOPRIO_BE_NR
#define cfq_class_idle(cfqq) ((cfqq)->ioprio_class == IOPRIO_CLASS_IDLE)
#define cfq_class_rt(cfqq) ((cfqq)->ioprio_class == IOPRIO_CLASS_RT)
#define sample_valid(samples) ((samples) > 80)
#define rb_entry_cfqg(node) rb_entry((node), struct cfq_group, rb_node)
/*
* Most of our rbtree usage is for sorting with min extraction, so
* if we cache the leftmost node we don't have to walk down the tree
* to find it. Idea borrowed from Ingo Molnars CFS scheduler. We should
* move this into the elevator for the rq sorting as well.
*/
struct cfq_rb_root {
struct rb_root rb;
struct rb_node *left;
unsigned count;
unsigned total_weight;
u64 min_vdisktime;
};
#define CFQ_RB_ROOT (struct cfq_rb_root) { .rb = RB_ROOT, .left = NULL, \
.count = 0, .min_vdisktime = 0, }
/*
* Per process-grouping structure
*/
struct cfq_queue {
/* reference count */
int ref;
/* various state flags, see below */
unsigned int flags;
/* parent cfq_data */
struct cfq_data *cfqd;
/* service_tree member */
struct rb_node rb_node;
/* service_tree key */
unsigned long rb_key;
/* prio tree member */
struct rb_node p_node;
/* prio tree root we belong to, if any */
struct rb_root *p_root;
/* sorted list of pending requests */
struct rb_root sort_list;
/* if fifo isn't expired, next request to serve */
struct request *next_rq;
/* requests queued in sort_list */
int queued[2];
/* currently allocated requests */
int allocated[2];
/* fifo list of requests in sort_list */
struct list_head fifo;
/* time when queue got scheduled in to dispatch first request. */
unsigned long dispatch_start;
unsigned int allocated_slice;
unsigned int slice_dispatch;
/* time when first request from queue completed and slice started. */
unsigned long slice_start;
unsigned long slice_end;
long slice_resid;
/* pending metadata requests */
int meta_pending;
/* number of requests that are on the dispatch list or inside driver */
int dispatched;
/* io prio of this group */
unsigned short ioprio, org_ioprio;
unsigned short ioprio_class, org_ioprio_class;
pid_t pid;
u32 seek_history;
sector_t last_request_pos;
struct cfq_rb_root *service_tree;
struct cfq_queue *new_cfqq;
struct cfq_group *cfqg;
/* Number of sectors dispatched from queue in single dispatch round */
unsigned long nr_sectors;
};
/*
* First index in the service_trees.
* IDLE is handled separately, so it has negative index
*/
enum wl_prio_t {
BE_WORKLOAD = 0,
RT_WORKLOAD = 1,
IDLE_WORKLOAD = 2,
CFQ_PRIO_NR,
};
/*
* Second index in the service_trees.
*/
enum wl_type_t {
ASYNC_WORKLOAD = 0,
SYNC_NOIDLE_WORKLOAD = 1,
SYNC_WORKLOAD = 2
};
/* This is per cgroup per device grouping structure */
struct cfq_group {
/* group service_tree member */
struct rb_node rb_node;
/* group service_tree key */
u64 vdisktime;
unsigned int weight;
unsigned int new_weight;
bool needs_update;
/* number of cfqq currently on this group */
int nr_cfqq;
/*
* Per group busy queus average. Useful for workload slice calc. We
* create the array for each prio class but at run time it is used
* only for RT and BE class and slot for IDLE class remains unused.
* This is primarily done to avoid confusion and a gcc warning.
*/
unsigned int busy_queues_avg[CFQ_PRIO_NR];
/*
* rr lists of queues with requests. We maintain service trees for
* RT and BE classes. These trees are subdivided in subclasses
* of SYNC, SYNC_NOIDLE and ASYNC based on workload type. For IDLE
* class there is no subclassification and all the cfq queues go on
* a single tree service_tree_idle.
* Counts are embedded in the cfq_rb_root
*/
struct cfq_rb_root service_trees[2][3];
struct cfq_rb_root service_tree_idle;
unsigned long saved_workload_slice;
enum wl_type_t saved_workload;
enum wl_prio_t saved_serving_prio;
struct blkio_group blkg;
#ifdef CONFIG_CFQ_GROUP_IOSCHED
struct hlist_node cfqd_node;
int ref;
#endif
/* number of requests that are on the dispatch list or inside driver */
int dispatched;
};
/*
* Per block device queue structure
*/
struct cfq_data {
struct request_queue *queue;
/* Root service tree for cfq_groups */
struct cfq_rb_root grp_service_tree;
struct cfq_group root_group;
/*
* The priority currently being served
*/
enum wl_prio_t serving_prio;
enum wl_type_t serving_type;
unsigned long workload_expires;
struct cfq_group *serving_group;
/*
* Each priority tree is sorted by next_request position. These
* trees are used when determining if two or more queues are
* interleaving requests (see cfq_close_cooperator).
*/
struct rb_root prio_trees[CFQ_PRIO_LISTS];
unsigned int busy_queues;
unsigned int busy_sync_queues;
int rq_in_driver;
int rq_in_flight[2];
/*
* queue-depth detection
*/
int rq_queued;
int hw_tag;
/*
* hw_tag can be
* -1 => indeterminate, (cfq will behave as if NCQ is present, to allow better detection)
* 1 => NCQ is present (hw_tag_est_depth is the estimated max depth)
* 0 => no NCQ
*/
int hw_tag_est_depth;
unsigned int hw_tag_samples;
/*
* idle window management
*/
struct timer_list idle_slice_timer;
struct work_struct unplug_work;
struct cfq_queue *active_queue;
struct cfq_io_context *active_cic;
/*
* async queue for each priority case
*/
struct cfq_queue *async_cfqq[2][IOPRIO_BE_NR];
struct cfq_queue *async_idle_cfqq;
sector_t last_position;
/*
* tunables, see top of file
*/
unsigned int cfq_quantum;
unsigned int cfq_fifo_expire[2];
unsigned int cfq_back_penalty;
unsigned int cfq_back_max;
unsigned int cfq_slice[2];
unsigned int cfq_slice_async_rq;
unsigned int cfq_slice_idle;
unsigned int cfq_group_idle;
unsigned int cfq_latency;
unsigned int cic_index;
struct list_head cic_list;
/*
* Fallback dummy cfqq for extreme OOM conditions
*/
struct cfq_queue oom_cfqq;
unsigned long last_delayed_sync;
/* List of cfq groups being managed on this device*/
struct hlist_head cfqg_list;
struct rcu_head rcu;
};
static struct cfq_group *cfq_get_next_cfqg(struct cfq_data *cfqd);
static struct cfq_rb_root *service_tree_for(struct cfq_group *cfqg,
enum wl_prio_t prio,
enum wl_type_t type)
{
if (!cfqg)
return NULL;
if (prio == IDLE_WORKLOAD)
return &cfqg->service_tree_idle;
return &cfqg->service_trees[prio][type];
}
enum cfqq_state_flags {
CFQ_CFQQ_FLAG_on_rr = 0, /* on round-robin busy list */
CFQ_CFQQ_FLAG_wait_request, /* waiting for a request */
CFQ_CFQQ_FLAG_must_dispatch, /* must be allowed a dispatch */
CFQ_CFQQ_FLAG_must_alloc_slice, /* per-slice must_alloc flag */
CFQ_CFQQ_FLAG_fifo_expire, /* FIFO checked in this slice */
CFQ_CFQQ_FLAG_idle_window, /* slice idling enabled */
CFQ_CFQQ_FLAG_prio_changed, /* task priority has changed */
CFQ_CFQQ_FLAG_slice_new, /* no requests dispatched in slice */
CFQ_CFQQ_FLAG_sync, /* synchronous queue */
CFQ_CFQQ_FLAG_coop, /* cfqq is shared */
CFQ_CFQQ_FLAG_split_coop, /* shared cfqq will be splitted */
CFQ_CFQQ_FLAG_deep, /* sync cfqq experienced large depth */
CFQ_CFQQ_FLAG_wait_busy, /* Waiting for next request */
};
#define CFQ_CFQQ_FNS(name) \
static inline void cfq_mark_cfqq_##name(struct cfq_queue *cfqq) \
{ \
(cfqq)->flags |= (1 << CFQ_CFQQ_FLAG_##name); \
} \
static inline void cfq_clear_cfqq_##name(struct cfq_queue *cfqq) \
{ \
(cfqq)->flags &= ~(1 << CFQ_CFQQ_FLAG_##name); \
} \
static inline int cfq_cfqq_##name(const struct cfq_queue *cfqq) \
{ \
return ((cfqq)->flags & (1 << CFQ_CFQQ_FLAG_##name)) != 0; \
}
CFQ_CFQQ_FNS(on_rr);
CFQ_CFQQ_FNS(wait_request);
CFQ_CFQQ_FNS(must_dispatch);
CFQ_CFQQ_FNS(must_alloc_slice);
CFQ_CFQQ_FNS(fifo_expire);
CFQ_CFQQ_FNS(idle_window);
CFQ_CFQQ_FNS(prio_changed);
CFQ_CFQQ_FNS(slice_new);
CFQ_CFQQ_FNS(sync);
CFQ_CFQQ_FNS(coop);
CFQ_CFQQ_FNS(split_coop);
CFQ_CFQQ_FNS(deep);
CFQ_CFQQ_FNS(wait_busy);
#undef CFQ_CFQQ_FNS
#ifdef CONFIG_CFQ_GROUP_IOSCHED
#define cfq_log_cfqq(cfqd, cfqq, fmt, args...) \
blk_add_trace_msg((cfqd)->queue, "cfq%d%c %s " fmt, (cfqq)->pid, \
cfq_cfqq_sync((cfqq)) ? 'S' : 'A', \
blkg_path(&(cfqq)->cfqg->blkg), ##args);
#define cfq_log_cfqg(cfqd, cfqg, fmt, args...) \
blk_add_trace_msg((cfqd)->queue, "%s " fmt, \
blkg_path(&(cfqg)->blkg), ##args); \
#else
#define cfq_log_cfqq(cfqd, cfqq, fmt, args...) \
blk_add_trace_msg((cfqd)->queue, "cfq%d " fmt, (cfqq)->pid, ##args)
#define cfq_log_cfqg(cfqd, cfqg, fmt, args...) do {} while (0);
#endif
#define cfq_log(cfqd, fmt, args...) \
blk_add_trace_msg((cfqd)->queue, "cfq " fmt, ##args)
/* Traverses through cfq group service trees */
#define for_each_cfqg_st(cfqg, i, j, st) \
for (i = 0; i <= IDLE_WORKLOAD; i++) \
for (j = 0, st = i < IDLE_WORKLOAD ? &cfqg->service_trees[i][j]\
: &cfqg->service_tree_idle; \
(i < IDLE_WORKLOAD && j <= SYNC_WORKLOAD) || \
(i == IDLE_WORKLOAD && j == 0); \
j++, st = i < IDLE_WORKLOAD ? \
&cfqg->service_trees[i][j]: NULL) \
static inline bool iops_mode(struct cfq_data *cfqd)
{
/*
* If we are not idling on queues and it is a NCQ drive, parallel
* execution of requests is on and measuring time is not possible
* in most of the cases until and unless we drive shallower queue
* depths and that becomes a performance bottleneck. In such cases
* switch to start providing fairness in terms of number of IOs.
*/
if (!cfqd->cfq_slice_idle && cfqd->hw_tag)
return true;
else
return false;
}
static inline enum wl_prio_t cfqq_prio(struct cfq_queue *cfqq)
{
if (cfq_class_idle(cfqq))
return IDLE_WORKLOAD;
if (cfq_class_rt(cfqq))
return RT_WORKLOAD;
return BE_WORKLOAD;
}
static enum wl_type_t cfqq_type(struct cfq_queue *cfqq)
{
if (!cfq_cfqq_sync(cfqq))
return ASYNC_WORKLOAD;
if (!cfq_cfqq_idle_window(cfqq))
return SYNC_NOIDLE_WORKLOAD;
return SYNC_WORKLOAD;
}
static inline int cfq_group_busy_queues_wl(enum wl_prio_t wl,
struct cfq_data *cfqd,
struct cfq_group *cfqg)
{
if (wl == IDLE_WORKLOAD)
return cfqg->service_tree_idle.count;
return cfqg->service_trees[wl][ASYNC_WORKLOAD].count
+ cfqg->service_trees[wl][SYNC_NOIDLE_WORKLOAD].count
+ cfqg->service_trees[wl][SYNC_WORKLOAD].count;
}
static inline int cfqg_busy_async_queues(struct cfq_data *cfqd,
struct cfq_group *cfqg)
{
return cfqg->service_trees[RT_WORKLOAD][ASYNC_WORKLOAD].count
+ cfqg->service_trees[BE_WORKLOAD][ASYNC_WORKLOAD].count;
}
static void cfq_dispatch_insert(struct request_queue *, struct request *);
static struct cfq_queue *cfq_get_queue(struct cfq_data *, bool,
struct io_context *, gfp_t);
static struct cfq_io_context *cfq_cic_lookup(struct cfq_data *,
struct io_context *);
static inline struct cfq_queue *cic_to_cfqq(struct cfq_io_context *cic,
bool is_sync)
{
return cic->cfqq[is_sync];
}
static inline void cic_set_cfqq(struct cfq_io_context *cic,
struct cfq_queue *cfqq, bool is_sync)
{
cic->cfqq[is_sync] = cfqq;
}
#define CIC_DEAD_KEY 1ul
#define CIC_DEAD_INDEX_SHIFT 1
static inline void *cfqd_dead_key(struct cfq_data *cfqd)
{
return (void *)(cfqd->cic_index << CIC_DEAD_INDEX_SHIFT | CIC_DEAD_KEY);
}
static inline struct cfq_data *cic_to_cfqd(struct cfq_io_context *cic)
{
struct cfq_data *cfqd = cic->key;
if (unlikely((unsigned long) cfqd & CIC_DEAD_KEY))
return NULL;
return cfqd;
}
/*
* We regard a request as SYNC, if it's either a read or has the SYNC bit
* set (in which case it could also be direct WRITE).
*/
static inline bool cfq_bio_sync(struct bio *bio)
{
return bio_data_dir(bio) == READ || (bio->bi_rw & REQ_SYNC);
}
/*
* scheduler run of queue, if there are requests pending and no one in the
* driver that will restart queueing
*/
static inline void cfq_schedule_dispatch(struct cfq_data *cfqd)
{
if (cfqd->busy_queues) {
cfq_log(cfqd, "schedule dispatch");
kblockd_schedule_work(cfqd->queue, &cfqd->unplug_work);
}
}
/*
* Scale schedule slice based on io priority. Use the sync time slice only
* if a queue is marked sync and has sync io queued. A sync queue with async
* io only, should not get full sync slice length.
*/
static inline int cfq_prio_slice(struct cfq_data *cfqd, bool sync,
unsigned short prio)
{
const int base_slice = cfqd->cfq_slice[sync];
WARN_ON(prio >= IOPRIO_BE_NR);
return base_slice + (base_slice/CFQ_SLICE_SCALE * (4 - prio));
}
static inline int
cfq_prio_to_slice(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
return cfq_prio_slice(cfqd, cfq_cfqq_sync(cfqq), cfqq->ioprio);
}
static inline u64 cfq_scale_slice(unsigned long delta, struct cfq_group *cfqg)
{
u64 d = delta << CFQ_SERVICE_SHIFT;
d = d * BLKIO_WEIGHT_DEFAULT;
do_div(d, cfqg->weight);
return d;
}
static inline u64 max_vdisktime(u64 min_vdisktime, u64 vdisktime)
{
s64 delta = (s64)(vdisktime - min_vdisktime);
if (delta > 0)
min_vdisktime = vdisktime;
return min_vdisktime;
}
static inline u64 min_vdisktime(u64 min_vdisktime, u64 vdisktime)
{
s64 delta = (s64)(vdisktime - min_vdisktime);
if (delta < 0)
min_vdisktime = vdisktime;
return min_vdisktime;
}
static void update_min_vdisktime(struct cfq_rb_root *st)
{
struct cfq_group *cfqg;
if (st->left) {
cfqg = rb_entry_cfqg(st->left);
st->min_vdisktime = max_vdisktime(st->min_vdisktime,
cfqg->vdisktime);
}
}
/*
* get averaged number of queues of RT/BE priority.
* average is updated, with a formula that gives more weight to higher numbers,
* to quickly follows sudden increases and decrease slowly
*/
static inline unsigned cfq_group_get_avg_queues(struct cfq_data *cfqd,
struct cfq_group *cfqg, bool rt)
{
unsigned min_q, max_q;
unsigned mult = cfq_hist_divisor - 1;
unsigned round = cfq_hist_divisor / 2;
unsigned busy = cfq_group_busy_queues_wl(rt, cfqd, cfqg);
min_q = min(cfqg->busy_queues_avg[rt], busy);
max_q = max(cfqg->busy_queues_avg[rt], busy);
cfqg->busy_queues_avg[rt] = (mult * max_q + min_q + round) /
cfq_hist_divisor;
return cfqg->busy_queues_avg[rt];
}
static inline unsigned
cfq_group_slice(struct cfq_data *cfqd, struct cfq_group *cfqg)
{
struct cfq_rb_root *st = &cfqd->grp_service_tree;
return cfq_target_latency * cfqg->weight / st->total_weight;
}
static inline unsigned
cfq_scaled_cfqq_slice(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
unsigned slice = cfq_prio_to_slice(cfqd, cfqq);
if (cfqd->cfq_latency) {
/*
* interested queues (we consider only the ones with the same
* priority class in the cfq group)
*/
unsigned iq = cfq_group_get_avg_queues(cfqd, cfqq->cfqg,
cfq_class_rt(cfqq));
unsigned sync_slice = cfqd->cfq_slice[1];
unsigned expect_latency = sync_slice * iq;
unsigned group_slice = cfq_group_slice(cfqd, cfqq->cfqg);
if (expect_latency > group_slice) {
unsigned base_low_slice = 2 * cfqd->cfq_slice_idle;
/* scale low_slice according to IO priority
* and sync vs async */
unsigned low_slice =
min(slice, base_low_slice * slice / sync_slice);
/* the adapted slice value is scaled to fit all iqs
* into the target latency */
slice = max(slice * group_slice / expect_latency,
low_slice);
}
}
return slice;
}
static inline void
cfq_set_prio_slice(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
unsigned slice = cfq_scaled_cfqq_slice(cfqd, cfqq);
cfqq->slice_start = jiffies;
cfqq->slice_end = jiffies + slice;
cfqq->allocated_slice = slice;
cfq_log_cfqq(cfqd, cfqq, "set_slice=%lu", cfqq->slice_end - jiffies);
}
/*
* We need to wrap this check in cfq_cfqq_slice_new(), since ->slice_end
* isn't valid until the first request from the dispatch is activated
* and the slice time set.
*/
static inline bool cfq_slice_used(struct cfq_queue *cfqq)
{
if (cfq_cfqq_slice_new(cfqq))
return false;
if (time_before(jiffies, cfqq->slice_end))
return false;
return true;
}
/*
* Lifted from AS - choose which of rq1 and rq2 that is best served now.
* We choose the request that is closest to the head right now. Distance
* behind the head is penalized and only allowed to a certain extent.
*/
static struct request *
cfq_choose_req(struct cfq_data *cfqd, struct request *rq1, struct request *rq2, sector_t last)
{
sector_t s1, s2, d1 = 0, d2 = 0;
unsigned long back_max;
#define CFQ_RQ1_WRAP 0x01 /* request 1 wraps */
#define CFQ_RQ2_WRAP 0x02 /* request 2 wraps */
unsigned wrap = 0; /* bit mask: requests behind the disk head? */
if (rq1 == NULL || rq1 == rq2)
return rq2;
if (rq2 == NULL)
return rq1;
if (rq_is_sync(rq1) && !rq_is_sync(rq2))
return rq1;
else if (rq_is_sync(rq2) && !rq_is_sync(rq1))
return rq2;
if ((rq1->cmd_flags & REQ_META) && !(rq2->cmd_flags & REQ_META))
return rq1;
else if ((rq2->cmd_flags & REQ_META) &&
!(rq1->cmd_flags & REQ_META))
return rq2;
s1 = blk_rq_pos(rq1);
s2 = blk_rq_pos(rq2);
/*
* by definition, 1KiB is 2 sectors
*/
back_max = cfqd->cfq_back_max * 2;
/*
* Strict one way elevator _except_ in the case where we allow
* short backward seeks which are biased as twice the cost of a
* similar forward seek.
*/
if (s1 >= last)
d1 = s1 - last;
else if (s1 + back_max >= last)
d1 = (last - s1) * cfqd->cfq_back_penalty;
else
wrap |= CFQ_RQ1_WRAP;
if (s2 >= last)
d2 = s2 - last;
else if (s2 + back_max >= last)
d2 = (last - s2) * cfqd->cfq_back_penalty;
else
wrap |= CFQ_RQ2_WRAP;
/* Found required data */
/*
* By doing switch() on the bit mask "wrap" we avoid having to
* check two variables for all permutations: --> faster!
*/
switch (wrap) {
case 0: /* common case for CFQ: rq1 and rq2 not wrapped */
if (d1 < d2)
return rq1;
else if (d2 < d1)
return rq2;
else {
if (s1 >= s2)
return rq1;
else
return rq2;
}
case CFQ_RQ2_WRAP:
return rq1;
case CFQ_RQ1_WRAP:
return rq2;
case (CFQ_RQ1_WRAP|CFQ_RQ2_WRAP): /* both rqs wrapped */
default:
/*
* Since both rqs are wrapped,
* start with the one that's further behind head
* (--> only *one* back seek required),
* since back seek takes more time than forward.
*/
if (s1 <= s2)
return rq1;
else
return rq2;
}
}
/*
* The below is leftmost cache rbtree addon
*/
static struct cfq_queue *cfq_rb_first(struct cfq_rb_root *root)
{
/* Service tree is empty */
if (!root->count)
return NULL;
if (!root->left)
root->left = rb_first(&root->rb);
if (root->left)
return rb_entry(root->left, struct cfq_queue, rb_node);
return NULL;
}
static struct cfq_group *cfq_rb_first_group(struct cfq_rb_root *root)
{
if (!root->left)
root->left = rb_first(&root->rb);
if (root->left)
return rb_entry_cfqg(root->left);
return NULL;
}
static void rb_erase_init(struct rb_node *n, struct rb_root *root)
{
rb_erase(n, root);
RB_CLEAR_NODE(n);
}
static void cfq_rb_erase(struct rb_node *n, struct cfq_rb_root *root)
{
if (root->left == n)
root->left = NULL;
rb_erase_init(n, &root->rb);
--root->count;
}
/*
* would be nice to take fifo expire time into account as well
*/
static struct request *
cfq_find_next_rq(struct cfq_data *cfqd, struct cfq_queue *cfqq,
struct request *last)
{
struct rb_node *rbnext = rb_next(&last->rb_node);
struct rb_node *rbprev = rb_prev(&last->rb_node);
struct request *next = NULL, *prev = NULL;
BUG_ON(RB_EMPTY_NODE(&last->rb_node));
if (rbprev)
prev = rb_entry_rq(rbprev);
if (rbnext)
next = rb_entry_rq(rbnext);
else {
rbnext = rb_first(&cfqq->sort_list);
if (rbnext && rbnext != &last->rb_node)
next = rb_entry_rq(rbnext);
}
return cfq_choose_req(cfqd, next, prev, blk_rq_pos(last));
}
static unsigned long cfq_slice_offset(struct cfq_data *cfqd,
struct cfq_queue *cfqq)
{
/*
* just an approximation, should be ok.
*/
return (cfqq->cfqg->nr_cfqq - 1) * (cfq_prio_slice(cfqd, 1, 0) -
cfq_prio_slice(cfqd, cfq_cfqq_sync(cfqq), cfqq->ioprio));
}
static inline s64
cfqg_key(struct cfq_rb_root *st, struct cfq_group *cfqg)
{
return cfqg->vdisktime - st->min_vdisktime;
}
static void
__cfq_group_service_tree_add(struct cfq_rb_root *st, struct cfq_group *cfqg)
{
struct rb_node **node = &st->rb.rb_node;
struct rb_node *parent = NULL;
struct cfq_group *__cfqg;
s64 key = cfqg_key(st, cfqg);
int left = 1;
while (*node != NULL) {
parent = *node;
__cfqg = rb_entry_cfqg(parent);
if (key < cfqg_key(st, __cfqg))
node = &parent->rb_left;
else {
node = &parent->rb_right;
left = 0;
}
}
if (left)
st->left = &cfqg->rb_node;
rb_link_node(&cfqg->rb_node, parent, node);
rb_insert_color(&cfqg->rb_node, &st->rb);
}
static void
cfq_update_group_weight(struct cfq_group *cfqg)
{
BUG_ON(!RB_EMPTY_NODE(&cfqg->rb_node));
if (cfqg->needs_update) {
cfqg->weight = cfqg->new_weight;
cfqg->needs_update = false;
}
}
static void
cfq_group_service_tree_add(struct cfq_rb_root *st, struct cfq_group *cfqg)
{
BUG_ON(!RB_EMPTY_NODE(&cfqg->rb_node));
cfq_update_group_weight(cfqg);
__cfq_group_service_tree_add(st, cfqg);
st->total_weight += cfqg->weight;
}
static void
cfq_group_notify_queue_add(struct cfq_data *cfqd, struct cfq_group *cfqg)
{
struct cfq_rb_root *st = &cfqd->grp_service_tree;
struct cfq_group *__cfqg;
struct rb_node *n;
cfqg->nr_cfqq++;
if (!RB_EMPTY_NODE(&cfqg->rb_node))
return;
/*
* Currently put the group at the end. Later implement something
* so that groups get lesser vtime based on their weights, so that
* if group does not loose all if it was not continuously backlogged.
*/
n = rb_last(&st->rb);
if (n) {
__cfqg = rb_entry_cfqg(n);
cfqg->vdisktime = __cfqg->vdisktime + CFQ_IDLE_DELAY;
} else
cfqg->vdisktime = st->min_vdisktime;
cfq_group_service_tree_add(st, cfqg);
}
static void
cfq_group_service_tree_del(struct cfq_rb_root *st, struct cfq_group *cfqg)
{
st->total_weight -= cfqg->weight;
if (!RB_EMPTY_NODE(&cfqg->rb_node))
cfq_rb_erase(&cfqg->rb_node, st);
}
static void
cfq_group_notify_queue_del(struct cfq_data *cfqd, struct cfq_group *cfqg)
{
struct cfq_rb_root *st = &cfqd->grp_service_tree;
BUG_ON(cfqg->nr_cfqq < 1);
cfqg->nr_cfqq--;
/* If there are other cfq queues under this group, don't delete it */
if (cfqg->nr_cfqq)
return;
cfq_log_cfqg(cfqd, cfqg, "del_from_rr group");
cfq_group_service_tree_del(st, cfqg);
cfqg->saved_workload_slice = 0;
cfq_blkiocg_update_dequeue_stats(&cfqg->blkg, 1);
}
static inline unsigned int cfq_cfqq_slice_usage(struct cfq_queue *cfqq,
unsigned int *unaccounted_time)
{
unsigned int slice_used;
/*
* Queue got expired before even a single request completed or
* got expired immediately after first request completion.
*/
if (!cfqq->slice_start || cfqq->slice_start == jiffies) {
/*
* Also charge the seek time incurred to the group, otherwise
* if there are mutiple queues in the group, each can dispatch
* a single request on seeky media and cause lots of seek time
* and group will never know it.
*/
slice_used = max_t(unsigned, (jiffies - cfqq->dispatch_start),
1);
} else {
slice_used = jiffies - cfqq->slice_start;
if (slice_used > cfqq->allocated_slice) {
*unaccounted_time = slice_used - cfqq->allocated_slice;
slice_used = cfqq->allocated_slice;
}
if (time_after(cfqq->slice_start, cfqq->dispatch_start))
*unaccounted_time += cfqq->slice_start -
cfqq->dispatch_start;
}
return slice_used;
}
static void cfq_group_served(struct cfq_data *cfqd, struct cfq_group *cfqg,
struct cfq_queue *cfqq)
{
struct cfq_rb_root *st = &cfqd->grp_service_tree;
unsigned int used_sl, charge, unaccounted_sl = 0;
int nr_sync = cfqg->nr_cfqq - cfqg_busy_async_queues(cfqd, cfqg)
- cfqg->service_tree_idle.count;
BUG_ON(nr_sync < 0);
used_sl = charge = cfq_cfqq_slice_usage(cfqq, &unaccounted_sl);
if (iops_mode(cfqd))
charge = cfqq->slice_dispatch;
else if (!cfq_cfqq_sync(cfqq) && !nr_sync)
charge = cfqq->allocated_slice;
/* Can't update vdisktime while group is on service tree */
cfq_group_service_tree_del(st, cfqg);
cfqg->vdisktime += cfq_scale_slice(charge, cfqg);
/* If a new weight was requested, update now, off tree */
cfq_group_service_tree_add(st, cfqg);
/* This group is being expired. Save the context */
if (time_after(cfqd->workload_expires, jiffies)) {
cfqg->saved_workload_slice = cfqd->workload_expires
- jiffies;
cfqg->saved_workload = cfqd->serving_type;
cfqg->saved_serving_prio = cfqd->serving_prio;
} else
cfqg->saved_workload_slice = 0;
cfq_log_cfqg(cfqd, cfqg, "served: vt=%llu min_vt=%llu", cfqg->vdisktime,
st->min_vdisktime);
cfq_log_cfqq(cfqq->cfqd, cfqq, "sl_used=%u disp=%u charge=%u iops=%u"
" sect=%u", used_sl, cfqq->slice_dispatch, charge,
iops_mode(cfqd), cfqq->nr_sectors);
cfq_blkiocg_update_timeslice_used(&cfqg->blkg, used_sl,
unaccounted_sl);
cfq_blkiocg_set_start_empty_time(&cfqg->blkg);
}
#ifdef CONFIG_CFQ_GROUP_IOSCHED
static inline struct cfq_group *cfqg_of_blkg(struct blkio_group *blkg)
{
if (blkg)
return container_of(blkg, struct cfq_group, blkg);
return NULL;
}
void cfq_update_blkio_group_weight(void *key, struct blkio_group *blkg,
unsigned int weight)
{
struct cfq_group *cfqg = cfqg_of_blkg(blkg);
cfqg->new_weight = weight;
cfqg->needs_update = true;
}
static struct cfq_group * cfq_find_alloc_cfqg(struct cfq_data *cfqd,
struct blkio_cgroup *blkcg, int create)
{
struct cfq_group *cfqg = NULL;
void *key = cfqd;
int i, j;
struct cfq_rb_root *st;
struct backing_dev_info *bdi = &cfqd->queue->backing_dev_info;
unsigned int major, minor;
cfqg = cfqg_of_blkg(blkiocg_lookup_group(blkcg, key));
if (cfqg && !cfqg->blkg.dev && bdi->dev && dev_name(bdi->dev)) {
sscanf(dev_name(bdi->dev), "%u:%u", &major, &minor);
cfqg->blkg.dev = MKDEV(major, minor);
goto done;
}
if (cfqg || !create)
goto done;
cfqg = kzalloc_node(sizeof(*cfqg), GFP_ATOMIC, cfqd->queue->node);
if (!cfqg)
goto done;
for_each_cfqg_st(cfqg, i, j, st)
*st = CFQ_RB_ROOT;
RB_CLEAR_NODE(&cfqg->rb_node);
/*
* Take the initial reference that will be released on destroy
* This can be thought of a joint reference by cgroup and
* elevator which will be dropped by either elevator exit
* or cgroup deletion path depending on who is exiting first.
*/
cfqg->ref = 1;
/*
* Add group onto cgroup list. It might happen that bdi->dev is
* not initialized yet. Initialize this new group without major
* and minor info and this info will be filled in once a new thread
* comes for IO. See code above.
*/
if (bdi->dev) {
sscanf(dev_name(bdi->dev), "%u:%u", &major, &minor);
cfq_blkiocg_add_blkio_group(blkcg, &cfqg->blkg, (void *)cfqd,
MKDEV(major, minor));
} else
cfq_blkiocg_add_blkio_group(blkcg, &cfqg->blkg, (void *)cfqd,
0);
cfqg->weight = blkcg_get_weight(blkcg, cfqg->blkg.dev);
/* Add group on cfqd list */
hlist_add_head(&cfqg->cfqd_node, &cfqd->cfqg_list);
done:
return cfqg;
}
/*
* Search for the cfq group current task belongs to. If create = 1, then also
* create the cfq group if it does not exist. request_queue lock must be held.
*/
static struct cfq_group *cfq_get_cfqg(struct cfq_data *cfqd, int create)
{
struct blkio_cgroup *blkcg;
struct cfq_group *cfqg = NULL;
rcu_read_lock();
blkcg = task_blkio_cgroup(current);
cfqg = cfq_find_alloc_cfqg(cfqd, blkcg, create);
if (!cfqg && create)
cfqg = &cfqd->root_group;
rcu_read_unlock();
return cfqg;
}
static inline struct cfq_group *cfq_ref_get_cfqg(struct cfq_group *cfqg)
{
cfqg->ref++;
return cfqg;
}
static void cfq_link_cfqq_cfqg(struct cfq_queue *cfqq, struct cfq_group *cfqg)
{
/* Currently, all async queues are mapped to root group */
if (!cfq_cfqq_sync(cfqq))
cfqg = &cfqq->cfqd->root_group;
cfqq->cfqg = cfqg;
/* cfqq reference on cfqg */
cfqq->cfqg->ref++;
}
static void cfq_put_cfqg(struct cfq_group *cfqg)
{
struct cfq_rb_root *st;
int i, j;
BUG_ON(cfqg->ref <= 0);
cfqg->ref--;
if (cfqg->ref)
return;
for_each_cfqg_st(cfqg, i, j, st)
BUG_ON(!RB_EMPTY_ROOT(&st->rb));
kfree(cfqg);
}
static void cfq_destroy_cfqg(struct cfq_data *cfqd, struct cfq_group *cfqg)
{
/* Something wrong if we are trying to remove same group twice */
BUG_ON(hlist_unhashed(&cfqg->cfqd_node));
hlist_del_init(&cfqg->cfqd_node);
/*
* Put the reference taken at the time of creation so that when all
* queues are gone, group can be destroyed.
*/
cfq_put_cfqg(cfqg);
}
static void cfq_release_cfq_groups(struct cfq_data *cfqd)
{
struct hlist_node *pos, *n;
struct cfq_group *cfqg;
hlist_for_each_entry_safe(cfqg, pos, n, &cfqd->cfqg_list, cfqd_node) {
/*
* If cgroup removal path got to blk_group first and removed
* it from cgroup list, then it will take care of destroying
* cfqg also.
*/
if (!cfq_blkiocg_del_blkio_group(&cfqg->blkg))
cfq_destroy_cfqg(cfqd, cfqg);
}
}
/*
* Blk cgroup controller notification saying that blkio_group object is being
* delinked as associated cgroup object is going away. That also means that
* no new IO will come in this group. So get rid of this group as soon as
* any pending IO in the group is finished.
*
* This function is called under rcu_read_lock(). key is the rcu protected
* pointer. That means "key" is a valid cfq_data pointer as long as we are rcu
* read lock.
*
* "key" was fetched from blkio_group under blkio_cgroup->lock. That means
* it should not be NULL as even if elevator was exiting, cgroup deltion
* path got to it first.
*/
void cfq_unlink_blkio_group(void *key, struct blkio_group *blkg)
{
unsigned long flags;
struct cfq_data *cfqd = key;
spin_lock_irqsave(cfqd->queue->queue_lock, flags);
cfq_destroy_cfqg(cfqd, cfqg_of_blkg(blkg));
spin_unlock_irqrestore(cfqd->queue->queue_lock, flags);
}
#else /* GROUP_IOSCHED */
static struct cfq_group *cfq_get_cfqg(struct cfq_data *cfqd, int create)
{
return &cfqd->root_group;
}
static inline struct cfq_group *cfq_ref_get_cfqg(struct cfq_group *cfqg)
{
return cfqg;
}
static inline void
cfq_link_cfqq_cfqg(struct cfq_queue *cfqq, struct cfq_group *cfqg) {
cfqq->cfqg = cfqg;
}
static void cfq_release_cfq_groups(struct cfq_data *cfqd) {}
static inline void cfq_put_cfqg(struct cfq_group *cfqg) {}
#endif /* GROUP_IOSCHED */
/*
* The cfqd->service_trees holds all pending cfq_queue's that have
* requests waiting to be processed. It is sorted in the order that
* we will service the queues.
*/
static void cfq_service_tree_add(struct cfq_data *cfqd, struct cfq_queue *cfqq,
bool add_front)
{
struct rb_node **p, *parent;
struct cfq_queue *__cfqq;
unsigned long rb_key;
struct cfq_rb_root *service_tree;
int left;
int new_cfqq = 1;
int group_changed = 0;
service_tree = service_tree_for(cfqq->cfqg, cfqq_prio(cfqq),
cfqq_type(cfqq));
if (cfq_class_idle(cfqq)) {
rb_key = CFQ_IDLE_DELAY;
parent = rb_last(&service_tree->rb);
if (parent && parent != &cfqq->rb_node) {
__cfqq = rb_entry(parent, struct cfq_queue, rb_node);
rb_key += __cfqq->rb_key;
} else
rb_key += jiffies;
} else if (!add_front) {
/*
* Get our rb key offset. Subtract any residual slice
* value carried from last service. A negative resid
* count indicates slice overrun, and this should position
* the next service time further away in the tree.
*/
rb_key = cfq_slice_offset(cfqd, cfqq) + jiffies;
rb_key -= cfqq->slice_resid;
cfqq->slice_resid = 0;
} else {
rb_key = -HZ;
__cfqq = cfq_rb_first(service_tree);
rb_key += __cfqq ? __cfqq->rb_key : jiffies;
}
if (!RB_EMPTY_NODE(&cfqq->rb_node)) {
new_cfqq = 0;
/*
* same position, nothing more to do
*/
if (rb_key == cfqq->rb_key &&
cfqq->service_tree == service_tree)
return;
cfq_rb_erase(&cfqq->rb_node, cfqq->service_tree);
cfqq->service_tree = NULL;
}
left = 1;
parent = NULL;
cfqq->service_tree = service_tree;
p = &service_tree->rb.rb_node;
while (*p) {
struct rb_node **n;
parent = *p;
__cfqq = rb_entry(parent, struct cfq_queue, rb_node);
/*
* sort by key, that represents service time.
*/
if (time_before(rb_key, __cfqq->rb_key))
n = &(*p)->rb_left;
else {
n = &(*p)->rb_right;
left = 0;
}
p = n;
}
if (left)
service_tree->left = &cfqq->rb_node;
cfqq->rb_key = rb_key;
rb_link_node(&cfqq->rb_node, parent, p);
rb_insert_color(&cfqq->rb_node, &service_tree->rb);
service_tree->count++;
if ((add_front || !new_cfqq) && !group_changed)
return;
cfq_group_notify_queue_add(cfqd, cfqq->cfqg);
}
static struct cfq_queue *
cfq_prio_tree_lookup(struct cfq_data *cfqd, struct rb_root *root,
sector_t sector, struct rb_node **ret_parent,
struct rb_node ***rb_link)
{
struct rb_node **p, *parent;
struct cfq_queue *cfqq = NULL;
parent = NULL;
p = &root->rb_node;
while (*p) {
struct rb_node **n;
parent = *p;
cfqq = rb_entry(parent, struct cfq_queue, p_node);
/*
* Sort strictly based on sector. Smallest to the left,
* largest to the right.
*/
if (sector > blk_rq_pos(cfqq->next_rq))
n = &(*p)->rb_right;
else if (sector < blk_rq_pos(cfqq->next_rq))
n = &(*p)->rb_left;
else
break;
p = n;
cfqq = NULL;
}
*ret_parent = parent;
if (rb_link)
*rb_link = p;
return cfqq;
}
static void cfq_prio_tree_add(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
struct rb_node **p, *parent;
struct cfq_queue *__cfqq;
if (cfqq->p_root) {
rb_erase(&cfqq->p_node, cfqq->p_root);
cfqq->p_root = NULL;
}
if (cfq_class_idle(cfqq))
return;
if (!cfqq->next_rq)
return;
cfqq->p_root = &cfqd->prio_trees[cfqq->org_ioprio];
__cfqq = cfq_prio_tree_lookup(cfqd, cfqq->p_root,
blk_rq_pos(cfqq->next_rq), &parent, &p);
if (!__cfqq) {
rb_link_node(&cfqq->p_node, parent, p);
rb_insert_color(&cfqq->p_node, cfqq->p_root);
} else
cfqq->p_root = NULL;
}
/*
* Update cfqq's position in the service tree.
*/
static void cfq_resort_rr_list(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
/*
* Resorting requires the cfqq to be on the RR list already.
*/
if (cfq_cfqq_on_rr(cfqq)) {
cfq_service_tree_add(cfqd, cfqq, 0);
cfq_prio_tree_add(cfqd, cfqq);
}
}
/*
* add to busy list of queues for service, trying to be fair in ordering
* the pending list according to last request service
*/
static void cfq_add_cfqq_rr(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
cfq_log_cfqq(cfqd, cfqq, "add_to_rr");
BUG_ON(cfq_cfqq_on_rr(cfqq));
cfq_mark_cfqq_on_rr(cfqq);
cfqd->busy_queues++;
if (cfq_cfqq_sync(cfqq))
cfqd->busy_sync_queues++;
cfq_resort_rr_list(cfqd, cfqq);
}
/*
* Called when the cfqq no longer has requests pending, remove it from
* the service tree.
*/
static void cfq_del_cfqq_rr(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
cfq_log_cfqq(cfqd, cfqq, "del_from_rr");
BUG_ON(!cfq_cfqq_on_rr(cfqq));
cfq_clear_cfqq_on_rr(cfqq);
if (!RB_EMPTY_NODE(&cfqq->rb_node)) {
cfq_rb_erase(&cfqq->rb_node, cfqq->service_tree);
cfqq->service_tree = NULL;
}
if (cfqq->p_root) {
rb_erase(&cfqq->p_node, cfqq->p_root);
cfqq->p_root = NULL;
}
cfq_group_notify_queue_del(cfqd, cfqq->cfqg);
BUG_ON(!cfqd->busy_queues);
cfqd->busy_queues--;
if (cfq_cfqq_sync(cfqq))
cfqd->busy_sync_queues--;
}
/*
* rb tree support functions
*/
static void cfq_del_rq_rb(struct request *rq)
{
struct cfq_queue *cfqq = RQ_CFQQ(rq);
const int sync = rq_is_sync(rq);
BUG_ON(!cfqq->queued[sync]);
cfqq->queued[sync]--;
elv_rb_del(&cfqq->sort_list, rq);
if (cfq_cfqq_on_rr(cfqq) && RB_EMPTY_ROOT(&cfqq->sort_list)) {
/*
* Queue will be deleted from service tree when we actually
* expire it later. Right now just remove it from prio tree
* as it is empty.
*/
if (cfqq->p_root) {
rb_erase(&cfqq->p_node, cfqq->p_root);
cfqq->p_root = NULL;
}
}
}
static void cfq_add_rq_rb(struct request *rq)
{
struct cfq_queue *cfqq = RQ_CFQQ(rq);
struct cfq_data *cfqd = cfqq->cfqd;
struct request *__alias, *prev;
cfqq->queued[rq_is_sync(rq)]++;
/*
* looks a little odd, but the first insert might return an alias.
* if that happens, put the alias on the dispatch list
*/
while ((__alias = elv_rb_add(&cfqq->sort_list, rq)) != NULL)
cfq_dispatch_insert(cfqd->queue, __alias);
if (!cfq_cfqq_on_rr(cfqq))
cfq_add_cfqq_rr(cfqd, cfqq);
/*
* check if this request is a better next-serve candidate
*/
prev = cfqq->next_rq;
cfqq->next_rq = cfq_choose_req(cfqd, cfqq->next_rq, rq, cfqd->last_position);
/*
* adjust priority tree position, if ->next_rq changes
*/
if (prev != cfqq->next_rq)
cfq_prio_tree_add(cfqd, cfqq);
BUG_ON(!cfqq->next_rq);
}
static void cfq_reposition_rq_rb(struct cfq_queue *cfqq, struct request *rq)
{
elv_rb_del(&cfqq->sort_list, rq);
cfqq->queued[rq_is_sync(rq)]--;
cfq_blkiocg_update_io_remove_stats(&(RQ_CFQG(rq))->blkg,
rq_data_dir(rq), rq_is_sync(rq));
cfq_add_rq_rb(rq);
cfq_blkiocg_update_io_add_stats(&(RQ_CFQG(rq))->blkg,
&cfqq->cfqd->serving_group->blkg, rq_data_dir(rq),
rq_is_sync(rq));
}
static struct request *
cfq_find_rq_fmerge(struct cfq_data *cfqd, struct bio *bio)
{
struct task_struct *tsk = current;
struct cfq_io_context *cic;
struct cfq_queue *cfqq;
cic = cfq_cic_lookup(cfqd, tsk->io_context);
if (!cic)
return NULL;
cfqq = cic_to_cfqq(cic, cfq_bio_sync(bio));
if (cfqq) {
sector_t sector = bio->bi_sector + bio_sectors(bio);
return elv_rb_find(&cfqq->sort_list, sector);
}
return NULL;
}
static void cfq_activate_request(struct request_queue *q, struct request *rq)
{
struct cfq_data *cfqd = q->elevator->elevator_data;
cfqd->rq_in_driver++;
cfq_log_cfqq(cfqd, RQ_CFQQ(rq), "activate rq, drv=%d",
cfqd->rq_in_driver);
cfqd->last_position = blk_rq_pos(rq) + blk_rq_sectors(rq);
}
static void cfq_deactivate_request(struct request_queue *q, struct request *rq)
{
struct cfq_data *cfqd = q->elevator->elevator_data;
WARN_ON(!cfqd->rq_in_driver);
cfqd->rq_in_driver--;
cfq_log_cfqq(cfqd, RQ_CFQQ(rq), "deactivate rq, drv=%d",
cfqd->rq_in_driver);
}
static void cfq_remove_request(struct request *rq)
{
struct cfq_queue *cfqq = RQ_CFQQ(rq);
if (cfqq->next_rq == rq)
cfqq->next_rq = cfq_find_next_rq(cfqq->cfqd, cfqq, rq);
list_del_init(&rq->queuelist);
cfq_del_rq_rb(rq);
cfqq->cfqd->rq_queued--;
cfq_blkiocg_update_io_remove_stats(&(RQ_CFQG(rq))->blkg,
rq_data_dir(rq), rq_is_sync(rq));
if (rq->cmd_flags & REQ_META) {
WARN_ON(!cfqq->meta_pending);
cfqq->meta_pending--;
}
}
static int cfq_merge(struct request_queue *q, struct request **req,
struct bio *bio)
{
struct cfq_data *cfqd = q->elevator->elevator_data;
struct request *__rq;
__rq = cfq_find_rq_fmerge(cfqd, bio);
if (__rq && elv_rq_merge_ok(__rq, bio)) {
*req = __rq;
return ELEVATOR_FRONT_MERGE;
}
return ELEVATOR_NO_MERGE;
}
static void cfq_merged_request(struct request_queue *q, struct request *req,
int type)
{
if (type == ELEVATOR_FRONT_MERGE) {
struct cfq_queue *cfqq = RQ_CFQQ(req);
cfq_reposition_rq_rb(cfqq, req);
}
}
static void cfq_bio_merged(struct request_queue *q, struct request *req,
struct bio *bio)
{
cfq_blkiocg_update_io_merged_stats(&(RQ_CFQG(req))->blkg,
bio_data_dir(bio), cfq_bio_sync(bio));
}
static void
cfq_merged_requests(struct request_queue *q, struct request *rq,
struct request *next)
{
struct cfq_queue *cfqq = RQ_CFQQ(rq);
/*
* reposition in fifo if next is older than rq
*/
if (!list_empty(&rq->queuelist) && !list_empty(&next->queuelist) &&
time_before(rq_fifo_time(next), rq_fifo_time(rq))) {
list_move(&rq->queuelist, &next->queuelist);
rq_set_fifo_time(rq, rq_fifo_time(next));
}
if (cfqq->next_rq == next)
cfqq->next_rq = rq;
cfq_remove_request(next);
cfq_blkiocg_update_io_merged_stats(&(RQ_CFQG(rq))->blkg,
rq_data_dir(next), rq_is_sync(next));
}
static int cfq_allow_merge(struct request_queue *q, struct request *rq,
struct bio *bio)
{
struct cfq_data *cfqd = q->elevator->elevator_data;
struct cfq_io_context *cic;
struct cfq_queue *cfqq;
/*
* Disallow merge of a sync bio into an async request.
*/
if (cfq_bio_sync(bio) && !rq_is_sync(rq))
return false;
/*
* Lookup the cfqq that this bio will be queued with. Allow
* merge only if rq is queued there.
*/
cic = cfq_cic_lookup(cfqd, current->io_context);
if (!cic)
return false;
cfqq = cic_to_cfqq(cic, cfq_bio_sync(bio));
return cfqq == RQ_CFQQ(rq);
}
static inline void cfq_del_timer(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
del_timer(&cfqd->idle_slice_timer);
cfq_blkiocg_update_idle_time_stats(&cfqq->cfqg->blkg);
}
static void __cfq_set_active_queue(struct cfq_data *cfqd,
struct cfq_queue *cfqq)
{
if (cfqq) {
cfq_log_cfqq(cfqd, cfqq, "set_active wl_prio:%d wl_type:%d",
cfqd->serving_prio, cfqd->serving_type);
cfq_blkiocg_update_avg_queue_size_stats(&cfqq->cfqg->blkg);
cfqq->slice_start = 0;
cfqq->dispatch_start = jiffies;
cfqq->allocated_slice = 0;
cfqq->slice_end = 0;
cfqq->slice_dispatch = 0;
cfqq->nr_sectors = 0;
cfq_clear_cfqq_wait_request(cfqq);
cfq_clear_cfqq_must_dispatch(cfqq);
cfq_clear_cfqq_must_alloc_slice(cfqq);
cfq_clear_cfqq_fifo_expire(cfqq);
cfq_mark_cfqq_slice_new(cfqq);
cfq_del_timer(cfqd, cfqq);
}
cfqd->active_queue = cfqq;
}
/*
* current cfqq expired its slice (or was too idle), select new one
*/
static void
__cfq_slice_expired(struct cfq_data *cfqd, struct cfq_queue *cfqq,
bool timed_out)
{
cfq_log_cfqq(cfqd, cfqq, "slice expired t=%d", timed_out);
if (cfq_cfqq_wait_request(cfqq))
cfq_del_timer(cfqd, cfqq);
cfq_clear_cfqq_wait_request(cfqq);
cfq_clear_cfqq_wait_busy(cfqq);
/*
* If this cfqq is shared between multiple processes, check to
* make sure that those processes are still issuing I/Os within
* the mean seek distance. If not, it may be time to break the
* queues apart again.
*/
if (cfq_cfqq_coop(cfqq) && CFQQ_SEEKY(cfqq))
cfq_mark_cfqq_split_coop(cfqq);
/*
* store what was left of this slice, if the queue idled/timed out
*/
if (timed_out) {
if (cfq_cfqq_slice_new(cfqq))
cfqq->slice_resid = cfq_scaled_cfqq_slice(cfqd, cfqq);
else
cfqq->slice_resid = cfqq->slice_end - jiffies;
cfq_log_cfqq(cfqd, cfqq, "resid=%ld", cfqq->slice_resid);
}
cfq_group_served(cfqd, cfqq->cfqg, cfqq);
if (cfq_cfqq_on_rr(cfqq) && RB_EMPTY_ROOT(&cfqq->sort_list))
cfq_del_cfqq_rr(cfqd, cfqq);
cfq_resort_rr_list(cfqd, cfqq);
if (cfqq == cfqd->active_queue)
cfqd->active_queue = NULL;
if (cfqd->active_cic) {
put_io_context(cfqd->active_cic->ioc);
cfqd->active_cic = NULL;
}
}
static inline void cfq_slice_expired(struct cfq_data *cfqd, bool timed_out)
{
struct cfq_queue *cfqq = cfqd->active_queue;
if (cfqq)
__cfq_slice_expired(cfqd, cfqq, timed_out);
}
/*
* Get next queue for service. Unless we have a queue preemption,
* we'll simply select the first cfqq in the service tree.
*/
static struct cfq_queue *cfq_get_next_queue(struct cfq_data *cfqd)
{
struct cfq_rb_root *service_tree =
service_tree_for(cfqd->serving_group, cfqd->serving_prio,
cfqd->serving_type);
if (!cfqd->rq_queued)
return NULL;
/* There is nothing to dispatch */
if (!service_tree)
return NULL;
if (RB_EMPTY_ROOT(&service_tree->rb))
return NULL;
return cfq_rb_first(service_tree);
}
static struct cfq_queue *cfq_get_next_queue_forced(struct cfq_data *cfqd)
{
struct cfq_group *cfqg;
struct cfq_queue *cfqq;
int i, j;
struct cfq_rb_root *st;
if (!cfqd->rq_queued)
return NULL;
cfqg = cfq_get_next_cfqg(cfqd);
if (!cfqg)
return NULL;
for_each_cfqg_st(cfqg, i, j, st)
if ((cfqq = cfq_rb_first(st)) != NULL)
return cfqq;
return NULL;
}
/*
* Get and set a new active queue for service.
*/
static struct cfq_queue *cfq_set_active_queue(struct cfq_data *cfqd,
struct cfq_queue *cfqq)
{
if (!cfqq)
cfqq = cfq_get_next_queue(cfqd);
__cfq_set_active_queue(cfqd, cfqq);
return cfqq;
}
static inline sector_t cfq_dist_from_last(struct cfq_data *cfqd,
struct request *rq)
{
if (blk_rq_pos(rq) >= cfqd->last_position)
return blk_rq_pos(rq) - cfqd->last_position;
else
return cfqd->last_position - blk_rq_pos(rq);
}
static inline int cfq_rq_close(struct cfq_data *cfqd, struct cfq_queue *cfqq,
struct request *rq)
{
return cfq_dist_from_last(cfqd, rq) <= CFQQ_CLOSE_THR;
}
static struct cfq_queue *cfqq_close(struct cfq_data *cfqd,
struct cfq_queue *cur_cfqq)
{
struct rb_root *root = &cfqd->prio_trees[cur_cfqq->org_ioprio];
struct rb_node *parent, *node;
struct cfq_queue *__cfqq;
sector_t sector = cfqd->last_position;
if (RB_EMPTY_ROOT(root))
return NULL;
/*
* First, if we find a request starting at the end of the last
* request, choose it.
*/
__cfqq = cfq_prio_tree_lookup(cfqd, root, sector, &parent, NULL);
if (__cfqq)
return __cfqq;
/*
* If the exact sector wasn't found, the parent of the NULL leaf
* will contain the closest sector.
*/
__cfqq = rb_entry(parent, struct cfq_queue, p_node);
if (cfq_rq_close(cfqd, cur_cfqq, __cfqq->next_rq))
return __cfqq;
if (blk_rq_pos(__cfqq->next_rq) < sector)
node = rb_next(&__cfqq->p_node);
else
node = rb_prev(&__cfqq->p_node);
if (!node)
return NULL;
__cfqq = rb_entry(node, struct cfq_queue, p_node);
if (cfq_rq_close(cfqd, cur_cfqq, __cfqq->next_rq))
return __cfqq;
return NULL;
}
/*
* cfqd - obvious
* cur_cfqq - passed in so that we don't decide that the current queue is
* closely cooperating with itself.
*
* So, basically we're assuming that that cur_cfqq has dispatched at least
* one request, and that cfqd->last_position reflects a position on the disk
* associated with the I/O issued by cur_cfqq. I'm not sure this is a valid
* assumption.
*/
static struct cfq_queue *cfq_close_cooperator(struct cfq_data *cfqd,
struct cfq_queue *cur_cfqq)
{
struct cfq_queue *cfqq;
if (cfq_class_idle(cur_cfqq))
return NULL;
if (!cfq_cfqq_sync(cur_cfqq))
return NULL;
if (CFQQ_SEEKY(cur_cfqq))
return NULL;
/*
* Don't search priority tree if it's the only queue in the group.
*/
if (cur_cfqq->cfqg->nr_cfqq == 1)
return NULL;
/*
* We should notice if some of the queues are cooperating, eg
* working closely on the same area of the disk. In that case,
* we can group them together and don't waste time idling.
*/
cfqq = cfqq_close(cfqd, cur_cfqq);
if (!cfqq)
return NULL;
/* If new queue belongs to different cfq_group, don't choose it */
if (cur_cfqq->cfqg != cfqq->cfqg)
return NULL;
/*
* It only makes sense to merge sync queues.
*/
if (!cfq_cfqq_sync(cfqq))
return NULL;
if (CFQQ_SEEKY(cfqq))
return NULL;
/*
* Do not merge queues of different priority classes
*/
if (cfq_class_rt(cfqq) != cfq_class_rt(cur_cfqq))
return NULL;
return cfqq;
}
/*
* Determine whether we should enforce idle window for this queue.
*/
static bool cfq_should_idle(struct cfq_data *cfqd, struct cfq_queue *cfqq)
{
enum wl_prio_t prio = cfqq_prio(cfqq);
struct cfq_rb_root *service_tree = cfqq->service_tree;
BUG_ON(!service_tree);
BUG_ON(!service_tree->count);
if (!cfqd->cfq_slice_idle)
return false;
/* We never do for idle class queues. */
if (prio == IDLE_WORKLOAD)
return false;
/* We do for queues that were marked with idle window flag. */
if (cfq_cfqq_idle_window(cfqq) &&
!(blk_queue_nonrot(cfqd->queue) && cfqd->hw_tag))
return true;
/*
* Otherwise, we do only if they are the last ones
* in their service tree.
*/
if (service_tree->count == 1 && cfq_cfqq_sync(cfqq))
return true;
cfq_log_cfqq(cfqd, cfqq, "Not idling. st->count:%d",
service_tree->count);
return false;
}
static void cfq_arm_slice_timer(struct cfq_data *cfqd)
{
struct cfq_queue *cfqq = cfqd->active_queue;
struct cfq_io_context *cic;
unsigned long sl, group_idle = 0;