Revision 8c0fdd85

b/target-mips/exec.h
68 68
#endif
69 69
void do_mfc0_random(void);
70 70
void do_mfc0_count(void);
71
void do_mtc0(int reg, int sel);
71
void do_mtc0_status_debug(uint32_t old, uint32_t val);
72
void do_mtc0_status_irqraise_debug(void);
72 73
void do_tlbwi (void);
73 74
void do_tlbwr (void);
74 75
void do_tlbp (void);
b/target-mips/op.c
852 852
    RETURN();
853 853
}
854 854

  
855
void op_mtc0 (void)
855
void op_mtc0_index (void)
856 856
{
857
    CALL_FROM_TB2(do_mtc0, PARAM1, PARAM2);
857
    env->CP0_index = (env->CP0_index & 0x80000000) | (T0 & 0x0000000F);
858
    RETURN();
859
}
860

  
861
void op_mtc0_entrylo0 (void)
862
{
863
    env->CP0_EntryLo0 = T0 & 0x3FFFFFFF;
864
    RETURN();
865
}
866

  
867
void op_mtc0_entrylo1 (void)
868
{
869
    env->CP0_EntryLo1 = T0 & 0x3FFFFFFF;
870
    RETURN();
871
}
872

  
873
void op_mtc0_context (void)
874
{
875
    env->CP0_Context = (env->CP0_Context & 0xFF800000) | (T0 & 0x007FFFF0);
876
    RETURN();
877
}
878

  
879
void op_mtc0_pagemask (void)
880
{
881
    env->CP0_PageMask = T0 & 0x01FFE000;
882
    RETURN();
883
}
884

  
885
void op_mtc0_wired (void)
886
{
887
    env->CP0_Wired = T0 & 0x0000000F;
888
    RETURN();
889
}
890

  
891
void op_mtc0_count (void)
892
{
893
    CALL_FROM_TB2(cpu_mips_store_count, env, T0);
894
    RETURN();
895
}
896

  
897
void op_mtc0_entryhi (void)
898
{
899
    uint32_t old, val;
900

  
901
    val = T0 & 0xFFFFE0FF;
902
    old = env->CP0_EntryHi;
903
    env->CP0_EntryHi = val;
904
    /* If the ASID changes, flush qemu's TLB.  */
905
    if ((old & 0xFF) != (val & 0xFF))
906
        CALL_FROM_TB2(cpu_mips_tlb_flush, env, 1);
907
    RETURN();
908
}
909

  
910
void op_mtc0_compare (void)
911
{
912
    CALL_FROM_TB2(cpu_mips_store_compare, env, T0);
913
    RETURN();
914
}
915

  
916
void op_mtc0_status (void)
917
{
918
    uint32_t val, old, mask;
919

  
920
    val = T0 & 0xFA78FF01;
921
    old = env->CP0_Status;
922
    if (T0 & (1 << CP0St_UM))
923
        env->hflags |= MIPS_HFLAG_UM;
924
    else
925
        env->hflags &= ~MIPS_HFLAG_UM;
926
    if (T0 & (1 << CP0St_ERL))
927
        env->hflags |= MIPS_HFLAG_ERL;
928
    else
929
        env->hflags &= ~MIPS_HFLAG_ERL;
930
    if (T0 & (1 << CP0St_EXL))
931
        env->hflags |= MIPS_HFLAG_EXL;
932
    else
933
        env->hflags &= ~MIPS_HFLAG_EXL;
934
    env->CP0_Status = val;
935
    /* If we unmasked an asserted IRQ, raise it */
936
    mask = 0x0000FF00;
937
    if (loglevel & CPU_LOG_TB_IN_ASM)
938
       CALL_FROM_TB2(do_mtc0_status_debug, old, val);
939
    if ((val & (1 << CP0St_IE)) && !(old & (1 << CP0St_IE)) &&
940
        !(env->hflags & MIPS_HFLAG_EXL) &&
941
        !(env->hflags & MIPS_HFLAG_ERL) &&
942
        !(env->hflags & MIPS_HFLAG_DM) &&
943
        (env->CP0_Status & env->CP0_Cause & mask)) {
944
        env->interrupt_request |= CPU_INTERRUPT_HARD;
945
       if (logfile)
946
           CALL_FROM_TB0(do_mtc0_status_irqraise_debug);
947
    } else if (!(val & (1 << CP0St_IE)) && (old & (1 << CP0St_IE))) {
948
        env->interrupt_request &= ~CPU_INTERRUPT_HARD;
949
    }
950
    RETURN();
951
}
952

  
953
void op_mtc0_cause (void)
954
{
955
    uint32_t val, old;
956

  
957
    val = (env->CP0_Cause & 0xB000F87C) | (T0 & 0x000C00300);
958
    old = env->CP0_Cause;
959
    env->CP0_Cause = val;
960
#if 0
961
    {
962
        int i, mask;
963

  
964
       /* Check if we ever asserted a software IRQ */
965
        for (i = 0; i < 2; i++) {
966
            mask = 0x100 << i;
967
            if ((val & mask) & !(old & mask))
968
                CALL_FROM_TB1(mips_set_irq, i);
969
        }
970
    }
971
#endif
972
    RETURN();
973
}
974

  
975
void op_mtc0_epc (void)
976
{
977
    env->CP0_EPC = T0;
978
    RETURN();
979
}
980

  
981
void op_mtc0_config0 (void)
982
{
983
#if defined(MIPS_USES_R4K_TLB)
984
    env->CP0_Config0 = (env->CP0_Config0 & 0x8017FF80) | (T0 & 0x7E000001);
985
#else
986
    env->CP0_Config0 = (env->CP0_Config0 & 0xFE17FF80) | (T0 & 0x00000001);
987
#endif
988
    RETURN();
989
}
990

  
991
void op_mtc0_watchlo (void)
992
{
993
    env->CP0_WatchLo = T0;
994
    RETURN();
995
}
996

  
997
void op_mtc0_watchhi (void)
998
{
999
    env->CP0_WatchHi = T0 & 0x40FF0FF8;
1000
    RETURN();
1001
}
1002

  
1003
void op_mtc0_debug (void)
1004
{
1005
    env->CP0_Debug = (env->CP0_Debug & 0x8C03FC1F) | (T0 & 0x13300120);
1006
    if (T0 & (1 << CP0DB_DM))
1007
        env->hflags |= MIPS_HFLAG_DM;
1008
    else
1009
        env->hflags &= ~MIPS_HFLAG_DM;
1010
    RETURN();
1011
}
1012

  
1013
void op_mtc0_depc (void)
1014
{
1015
    env->CP0_DEPC = T0;
1016
    RETURN();
1017
}
1018

  
1019
void op_mtc0_taglo (void)
1020
{
1021
    env->CP0_TagLo = T0 & 0xFFFFFCF6;
1022
    RETURN();
1023
}
1024

  
1025
void op_mtc0_errorepc (void)
1026
{
1027
    env->CP0_ErrorEPC = T0;
1028
    RETURN();
1029
}
1030

  
1031
void op_mtc0_desave (void)
1032
{
1033
    env->CP0_DESAVE = T0;
858 1034
    RETURN();
859 1035
}
860 1036

  
b/target-mips/op_helper.c
141 141
    cpu_abort(env, "mfc0 count\n");
142 142
}
143 143

  
144
void do_mtc0 (int reg, int sel)
144
void cpu_mips_store_count(CPUState *env, uint32_t value)
145 145
{
146
    cpu_abort(env, "mtc0 reg=%d sel=%d\n", reg, sel);
146
    cpu_abort(env, "mtc0 count\n");
147
}
148

  
149
void cpu_mips_store_compare(CPUState *env, uint32_t value)
150
{
151
    cpu_abort(env, "mtc0 compare\n");
152
}
153

  
154
void do_mtc0_status_debug(uint32_t old, uint32_t val)
155
{
156
    cpu_abort(env, "mtc0 status\n");
157
}
158

  
159
void do_mtc0_status_irqraise_debug(void)
160
{
161
    cpu_abort(env, "mtc0 status\n");
147 162
}
148 163

  
149 164
void do_tlbwi (void)
......
166 181
    cpu_abort(env, "tlbr\n");
167 182
}
168 183

  
184
void cpu_mips_tlb_flush (CPUState *env, int flush_global)
185
{
186
    cpu_abort(env, "mips_tlb_flush\n");
187
}
188

  
169 189
#else
170 190

  
171 191
/* CP0 helpers */
......
179 199
    T0 = cpu_mips_get_count(env);
180 200
}
181 201

  
182
void do_mtc0 (int reg, int sel)
202
void do_mtc0_status_debug(uint32_t old, uint32_t val)
183 203
{
184
    const unsigned char *rn;
185
    uint32_t val, old, mask;
204
    const uint32_t mask = 0x0000FF00;
205
    fprintf(logfile, "Status %08x => %08x Cause %08x (%08x %08x %08x)\n",
206
            old, val, env->CP0_Cause, old & mask, val & mask,
207
            env->CP0_Cause & mask);
208
}
186 209

  
187
    if (sel != 0 && reg != 16 && reg != 28) {
188
        val = -1;
189
        old = -1;
190
        rn = "invalid";
191
        goto print;
192
    }
193
    switch (reg) {
194
    case 0:
195
        val = (env->CP0_index & 0x80000000) | (T0 & 0x0000000F);
196
        old = env->CP0_index;
197
        env->CP0_index = val;
198
        rn = "Index";
199
        break;
200
    case 2:
201
        val = T0 & 0x3FFFFFFF;
202
        old = env->CP0_EntryLo0;
203
        env->CP0_EntryLo0 = val;
204
        rn = "EntryLo0";
205
        break;
206
    case 3:
207
        val = T0 & 0x3FFFFFFF;
208
        old = env->CP0_EntryLo1;
209
        env->CP0_EntryLo1 = val;
210
        rn = "EntryLo1";
211
        break;
212
    case 4:
213
        val = (env->CP0_Context & 0xFF800000) | (T0 & 0x007FFFF0);
214
        old = env->CP0_Context;
215
        env->CP0_Context = val;
216
        rn = "Context";
217
        break;
218
    case 5:
219
        val = T0 & 0x01FFE000;
220
        old = env->CP0_PageMask;
221
        env->CP0_PageMask = val;
222
        rn = "PageMask";
223
        break;
224
    case 6:
225
        val = T0 & 0x0000000F;
226
        old = env->CP0_Wired;
227
        env->CP0_Wired = val;
228
        rn = "Wired";
229
        break;
230
    case 9:
231
        val = T0;
232
        old = cpu_mips_get_count(env);
233
        cpu_mips_store_count(env, val);
234
        rn = "Count";
235
        break;
236
    case 10:
237
        val = T0 & 0xFFFFE0FF;
238
        old = env->CP0_EntryHi;
239
        env->CP0_EntryHi = val;
240
	/* If the ASID changes, flush qemu's TLB.  */
241
	if ((old & 0xFF) != (val & 0xFF))
242
	  cpu_mips_tlb_flush (env, 1);
243
        rn = "EntryHi";
244
        break;
245
    case 11:
246
        val = T0;
247
        old = env->CP0_Compare;
248
        cpu_mips_store_compare(env, val);
249
        rn = "Compare";
250
        break;
251
    case 12:
252
        val = T0 & 0xFA78FF01;
253
        if (T0 & (1 << CP0St_UM))
254
            env->hflags |= MIPS_HFLAG_UM;
255
        else
256
            env->hflags &= ~MIPS_HFLAG_UM;
257
        if (T0 & (1 << CP0St_ERL))
258
            env->hflags |= MIPS_HFLAG_ERL;
259
        else
260
            env->hflags &= ~MIPS_HFLAG_ERL;
261
        if (T0 & (1 << CP0St_EXL))
262
            env->hflags |= MIPS_HFLAG_EXL;
263
        else
264
            env->hflags &= ~MIPS_HFLAG_EXL;
265
        old = env->CP0_Status;
266
        env->CP0_Status = val;
267
        /* If we unmasked an asserted IRQ, raise it */
268
        mask = 0x0000FF00;
269
        if (loglevel & CPU_LOG_TB_IN_ASM) {
270
            fprintf(logfile, "Status %08x => %08x Cause %08x (%08x %08x %08x)\n",
271
                    old, val, env->CP0_Cause, old & mask, val & mask,
272
                    env->CP0_Cause & mask);
273
        }
274
        if ((val & (1 << CP0St_IE)) && !(old & (1 << CP0St_IE)) &&
275
            !(env->hflags & MIPS_HFLAG_EXL) &&
276
            !(env->hflags & MIPS_HFLAG_ERL) &&
277
            !(env->hflags & MIPS_HFLAG_DM) &&
278
            (env->CP0_Status & env->CP0_Cause & mask)) {
279
            if (logfile)
280
                fprintf(logfile, "Raise pending IRQs\n");
281
            env->interrupt_request |= CPU_INTERRUPT_HARD;
282
        } else if (!(val & (1 << CP0St_IE)) && (old & (1 << CP0St_IE))) {
283
            env->interrupt_request &= ~CPU_INTERRUPT_HARD;
284
        }
285
        rn = "Status";
286
        break;
287
    case 13:
288
        val = (env->CP0_Cause & 0xB000F87C) | (T0 & 0x000C00300);
289
        old = env->CP0_Cause;
290
        env->CP0_Cause = val;
291
#if 0
292
        {
293
            int i;
294
            /* Check if we ever asserted a software IRQ */
295
            for (i = 0; i < 2; i++) {
296
                mask = 0x100 << i;
297
                if ((val & mask) & !(old & mask))
298
                    mips_set_irq(i);
299
            }
300
        }
301
#endif
302
        rn = "Cause";
303
        break;
304
    case 14:
305
        val = T0;
306
        old = env->CP0_EPC;
307
        env->CP0_EPC = val;
308
        rn = "EPC";
309
        break;
310
    case 16:
311
        switch (sel) {
312
        case 0:
313
#if defined(MIPS_USES_R4K_TLB)
314
            val = (env->CP0_Config0 & 0x8017FF80) | (T0 & 0x7E000001);
315
#else
316
            val = (env->CP0_Config0 & 0xFE17FF80) | (T0 & 0x00000001);
317
#endif
318
            old = env->CP0_Config0;
319
            env->CP0_Config0 = val;
320
            rn = "Config0";
321
            break;
322
        default:
323
            val = -1;
324
            old = -1;
325
            rn = "bad config selector";
326
            break;
327
        }
328
        break;
329
    case 18:
330
        val = T0;
331
        old = env->CP0_WatchLo;
332
        env->CP0_WatchLo = val;
333
        rn = "WatchLo";
334
        break;
335
    case 19:
336
        val = T0 & 0x40FF0FF8;
337
        old = env->CP0_WatchHi;
338
        env->CP0_WatchHi = val;
339
        rn = "WatchHi";
340
        break;
341
    case 23:
342
        val = (env->CP0_Debug & 0x8C03FC1F) | (T0 & 0x13300120);
343
        if (T0 & (1 << CP0DB_DM))
344
            env->hflags |= MIPS_HFLAG_DM;
345
        else
346
            env->hflags &= ~MIPS_HFLAG_DM;
347
        old = env->CP0_Debug;
348
        env->CP0_Debug = val;
349
        rn = "Debug";
350
        break;
351
    case 24:
352
        val = T0;
353
        old = env->CP0_DEPC;
354
        env->CP0_DEPC = val;
355
        rn = "DEPC";
356
        break;
357
    case 28:
358
        switch (sel) {
359
        case 0:
360
            val = T0 & 0xFFFFFCF6;
361
            old = env->CP0_TagLo;
362
            env->CP0_TagLo = val;
363
            rn = "TagLo";
364
            break;
365
        default:
366
            val = -1;
367
            old = -1;
368
            rn = "invalid sel";
369
            break;
370
        }
371
        break;
372
    case 30:
373
        val = T0;
374
        old = env->CP0_ErrorEPC;
375
        env->CP0_ErrorEPC = val;
376
        rn = "EPC";
377
        break;
378
    case 31:
379
        val = T0;
380
        old = env->CP0_DESAVE;
381
        env->CP0_DESAVE = val;
382
        rn = "DESAVE";
383
        break;
384
    default:
385
        val = -1;
386
        old = -1;
387
        rn = "unknown";
388
        break;
389
    }
390
 print:
391
#if defined MIPS_DEBUG_DISAS
392
    if (loglevel & CPU_LOG_TB_IN_ASM) {
393
        fprintf(logfile, "%08x mtc0 %s %08x => %08x (%d %d %08x)\n",
394
                env->PC, rn, T0, val, reg, sel, old);
395
    }
396
#endif
397
    return;
210
void do_mtc0_status_irqraise_debug(void)
211
{
212
    fprintf(logfile, "Raise pending IRQs\n");
398 213
}
399 214

  
400 215
#ifdef MIPS_USES_FPU
b/target-mips/translate.c
1371 1371
        rn = "EntryLo0";
1372 1372
        break;
1373 1373
    case 3:
1374
       /* also CONF */
1374 1375
        gen_op_mfc0_entrylo1();
1375 1376
        rn = "EntryLo1";
1376 1377
        break;
......
1386 1387
        gen_op_mfc0_wired();
1387 1388
        rn = "Wired";
1388 1389
        break;
1390
    case 7:
1391
//        gen_op_mfc0_info();
1392
        rn = "Info";
1393
        break;
1389 1394
    case 8:
1390 1395
        gen_op_mfc0_badvaddr();
1391 1396
        rn = "BadVaddr";
......
1445 1450
        gen_op_mfc0_watchhi();
1446 1451
        rn = "WatchHi";
1447 1452
        break;
1453
    case 20:
1454
       /* 64 bit only */
1455
//        gen_op_mfc0_xcontext();
1456
        rn = "XContext";
1457
        break;
1458
    case 21:
1459
//        gen_op_mfc0_framemask();
1460
        rn = "Framemask";
1461
        break;
1462
    case 22:
1463
//        gen_op_mfc0_diagnostic();
1464
        rn = "'Diagnostic";
1465
        break;
1448 1466
    case 23:
1449 1467
        gen_op_mfc0_debug();
1450 1468
        rn = "Debug";
......
1453 1471
        gen_op_mfc0_depc();
1454 1472
        rn = "DEPC";
1455 1473
        break;
1474
    case 25:
1475
//        gen_op_mfc0_performance();
1476
        rn = "Performance";
1477
        break;
1478
    case 26:
1479
//        gen_op_mfc0_ecc();
1480
        rn = "ECC";
1481
        break;
1482
    case 27:
1483
//        gen_op_mfc0_cacheerr();
1484
        rn = "CacheErr";
1485
        break;
1456 1486
    case 28:
1457 1487
        switch (sel) {
1458 1488
        case 0:
......
1468 1498
            goto die;
1469 1499
        }
1470 1500
        break;
1501
    case 29:
1502
//        gen_op_mfc0_taghi();
1503
        rn = "TagHi";
1504
        break;
1471 1505
    case 30:
1472 1506
        gen_op_mfc0_errorepc();
1473 1507
        rn = "ErrorEPC";
......
1498 1532
    generate_exception(ctx, EXCP_RI);
1499 1533
}
1500 1534

  
1535
static void gen_mtc0 (DisasContext *ctx, int reg, int sel)
1536
{
1537
    const unsigned char *rn;
1538
    uint32_t val, old;
1539

  
1540
    if (sel != 0 && reg != 16 && reg != 28) {
1541
        val = -1;
1542
        old = -1;
1543
        rn = "invalid";
1544
        goto die;
1545
    }
1546
    switch (reg) {
1547
    case 0:
1548
        gen_op_mtc0_index();
1549
        rn = "Index";
1550
        break;
1551
    case 1:
1552
// ignore or except?
1553
        rn = "Random";
1554
        break;
1555
    case 2:
1556
        gen_op_mtc0_entrylo0();
1557
        rn = "EntryLo0";
1558
        break;
1559
    case 3:
1560
        gen_op_mtc0_entrylo1();
1561
        rn = "EntryLo1";
1562
        break;
1563
    case 4:
1564
        gen_op_mtc0_context();
1565
        rn = "Context";
1566
        break;
1567
    case 5:
1568
        gen_op_mtc0_pagemask();
1569
        rn = "PageMask";
1570
        break;
1571
    case 6:
1572
        gen_op_mtc0_wired();
1573
        rn = "Wired";
1574
        break;
1575
    case 7:
1576
// ignore or except?
1577
        rn = "Info";
1578
        break;
1579
    case 8:
1580
// ignore or except?
1581
        rn = "BadVaddr";
1582
        break;
1583
    case 9:
1584
        gen_op_mtc0_count();
1585
        rn = "Count";
1586
        break;
1587
    case 10:
1588
        gen_op_mtc0_entryhi();
1589
        rn = "EntryHi";
1590
        break;
1591
    case 11:
1592
        gen_op_mtc0_compare();
1593
        rn = "Compare";
1594
        break;
1595
    case 12:
1596
        gen_op_mtc0_status();
1597
        rn = "Status";
1598
        break;
1599
    case 13:
1600
        gen_op_mtc0_cause();
1601
        rn = "Cause";
1602
        break;
1603
    case 14:
1604
        gen_op_mtc0_epc();
1605
        rn = "EPC";
1606
        break;
1607
    case 15:
1608
// ignore or except?
1609
        rn = "PRid";
1610
        break;
1611
    case 16:
1612
        switch (sel) {
1613
        case 0:
1614
           gen_op_mtc0_config0();
1615
            rn = "Config0";
1616
            break;
1617
        default:
1618
            rn = "Invalid config selector";
1619
            goto die;
1620
        }
1621
        break;
1622
    case 17:
1623
// ignore or except?
1624
        rn = "LLaddr";
1625
        break;
1626
    case 18:
1627
        gen_op_mtc0_watchlo();
1628
        rn = "WatchLo";
1629
        break;
1630
    case 19:
1631
        gen_op_mtc0_watchhi();
1632
        rn = "WatchHi";
1633
        break;
1634
    case 20:
1635
       /* 64 bit only */
1636
//     gen_op_mtc0_xcontext();
1637
        rn = "XContext";
1638
        break;
1639
    case 21:
1640
//        gen_op_mtc0_framemask();
1641
        rn = "Framemask";
1642
       break;
1643
    case 22:
1644
// ignore or except?
1645
        rn = "Diagnostic";
1646
       break;
1647
    case 23:
1648
        gen_op_mtc0_debug();
1649
        rn = "Debug";
1650
        break;
1651
    case 24:
1652
        gen_op_mtc0_depc();
1653
        rn = "DEPC";
1654
        break;
1655
    case 25:
1656
// ignore or except?
1657
        rn = "Performance";
1658
       break;
1659
    case 26:
1660
// ignore or except?
1661
        rn = "ECC";
1662
       break;
1663
    case 27:
1664
// ignore or except?
1665
        rn = "CacheErr";
1666
       break;
1667
    case 28:
1668
        switch (sel) {
1669
        case 0:
1670
            gen_op_mtc0_taglo();
1671
            rn = "TagLo";
1672
            break;
1673
        default:
1674
            rn = "invalid sel";
1675
            goto die;
1676
        }
1677
        break;
1678
    case 29:
1679
//     gen_op_mtc0_taghi();
1680
        rn = "TagHi";
1681
       break;
1682
    case 30:
1683
        gen_op_mtc0_errorepc();
1684
        rn = "ErrorEPC";
1685
        break;
1686
    case 31:
1687
        gen_op_mtc0_desave();
1688
        rn = "DESAVE";
1689
        break;
1690
    default:
1691
        rn = "unknown";
1692
       goto die;
1693
    }
1694
#if defined MIPS_DEBUG_DISAS
1695
    if (loglevel & CPU_LOG_TB_IN_ASM) {
1696
        fprintf(logfile, "%08x mtc0 %s => %08x (%d %d)\n",
1697
                env->PC, rn, T0, reg, sel);
1698
    }
1699
#endif
1700
    return;
1701

  
1702
die:
1703
#if defined MIPS_DEBUG_DISAS
1704
    if (loglevel & CPU_LOG_TB_IN_ASM) {
1705
        fprintf(logfile, "%08x mtc0 %s => %08x (%d %d)\n",
1706
                env->PC, rn, T0, reg, sel);
1707
    }
1708
#endif
1709
    generate_exception(ctx, EXCP_RI);
1710
}
1711

  
1501 1712
static void gen_cp0 (DisasContext *ctx, uint16_t opc, int rt, int rd)
1502 1713
{
1503 1714
    const unsigned char *opn = "unk";
......
1529 1740
        save_cpu_state(ctx, 1);
1530 1741
        ctx->pc -= 4;
1531 1742
        GEN_LOAD_REG_TN(T0, rt);
1532
        gen_op_mtc0(rd, ctx->opcode & 0x7);
1743
        gen_mtc0(ctx, rd, ctx->opcode & 0x7);
1533 1744
        /* Stop translation as we may have switched the execution mode */
1534 1745
        ctx->bstate = BS_STOP;
1535 1746
        opn = "mtc0";

Also available in: Unified diff