Hardware/LPC/NCT677X.cs
author moel.mich
Thu, 26 Jul 2012 06:50:56 +0000
changeset 376 df64b56ece65
parent 355 17dbf781401e
child 413 362c5e77197d
permissions -rw-r--r--
Fixed an error where loading config files from previous versions would crash the application.
moel@245
     1
/*
moel@245
     2
 
moel@344
     3
  This Source Code Form is subject to the terms of the Mozilla Public
moel@344
     4
  License, v. 2.0. If a copy of the MPL was not distributed with this
moel@344
     5
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
moel@245
     6
 
moel@355
     7
  Copyright (C) 2010-2012 Michael Möller <mmoeller@openhardwaremonitor.org>
moel@344
     8
	
moel@245
     9
*/
moel@245
    10
moel@355
    11
using System;
moel@245
    12
using System.Globalization;
moel@245
    13
using System.Text;
moel@245
    14
moel@245
    15
namespace OpenHardwareMonitor.Hardware.LPC {
moel@245
    16
  internal class NCT677X : ISuperIO {
moel@245
    17
moel@245
    18
    private readonly ushort port;
moel@245
    19
    private readonly byte revision;
moel@245
    20
moel@245
    21
    private readonly Chip chip;
moel@245
    22
moel@355
    23
    private readonly float?[] voltages;
moel@355
    24
    private readonly float?[] temperatures;
moel@355
    25
    private readonly float?[] fans ;
moel@355
    26
    private readonly float?[] controls;
moel@245
    27
moel@245
    28
    // Hardware Monitor
moel@245
    29
    private const uint ADDRESS_REGISTER_OFFSET = 0x05;
moel@245
    30
    private const uint DATA_REGISTER_OFFSET = 0x06;
moel@245
    31
    private const byte BANK_SELECT_REGISTER = 0x4E;
moel@245
    32
moel@245
    33
    private byte ReadByte(ushort address) {
moel@245
    34
      byte bank = (byte)(address >> 8);
moel@245
    35
      byte register = (byte)(address & 0xFF);
moel@245
    36
      Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, BANK_SELECT_REGISTER);
moel@245
    37
      Ring0.WriteIoPort(port + DATA_REGISTER_OFFSET, bank);
moel@245
    38
      Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, register);
moel@245
    39
      return Ring0.ReadIoPort(port + DATA_REGISTER_OFFSET);
moel@323
    40
    }
moel@323
    41
moel@323
    42
    private void WriteByte(ushort address, byte value) {
moel@323
    43
      byte bank = (byte)(address >> 8);
moel@323
    44
      byte register = (byte)(address & 0xFF);
moel@323
    45
      Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, BANK_SELECT_REGISTER);
moel@323
    46
      Ring0.WriteIoPort(port + DATA_REGISTER_OFFSET, bank);
moel@323
    47
      Ring0.WriteIoPort(port + ADDRESS_REGISTER_OFFSET, register);
moel@323
    48
      Ring0.WriteIoPort(port + DATA_REGISTER_OFFSET, value);
moel@356
    49
    }
moel@245
    50
moel@245
    51
    // Consts 
moel@245
    52
    private const ushort NUVOTON_VENDOR_ID = 0x5CA3;
moel@245
    53
moel@245
    54
    // Hardware Monitor Registers    
moel@245
    55
    private const ushort VENDOR_ID_HIGH_REGISTER = 0x804F;
moel@355
    56
    private const ushort VENDOR_ID_LOW_REGISTER = 0x004F;  
moel@355
    57
    
moel@355
    58
    private readonly ushort[] FAN_PWM_OUT_REG = 
moel@355
    59
      { 0x001, 0x003, 0x011, 0x013, 0x015 };
moel@355
    60
    private readonly ushort[] FAN_PWM_COMMAND_REG = 
moel@355
    61
      { 0x109, 0x209, 0x309, 0x809, 0x909 };
moel@355
    62
    private readonly ushort[] FAN_CONTROL_MODE_REG = 
moel@355
    63
      { 0x102, 0x202, 0x302, 0x802, 0x902 };
moel@245
    64
moel@355
    65
    private readonly ushort fanRpmBaseRegister;
moel@265
    66
    private readonly int minFanRPM;
moel@245
    67
moel@355
    68
    private bool[] restoreDefaultFanControlRequired = new bool[5];       
moel@355
    69
    private byte[] initialFanControlMode = new byte[5];
moel@355
    70
    private byte[] initialFanPwmCommand = new byte[5];
moel@355
    71
moel@355
    72
    private readonly ushort[] voltageRegisters;
moel@355
    73
    private readonly ushort voltageVBatRegister;
moel@355
    74
moel@355
    75
    private readonly byte[] temperaturesSource;
moel@355
    76
moel@355
    77
    private readonly ushort[] temperatureRegister;
moel@355
    78
    private readonly ushort[] temperatureHalfRegister;
moel@355
    79
    private readonly int[] temperatureHalfBit;  
moel@355
    80
    private readonly ushort[] temperatureSourceRegister;        
moel@355
    81
moel@355
    82
    private readonly ushort?[] alternateTemperatureRegister;
moel@323
    83
moel@276
    84
    private enum SourceNCT6771F : byte {
moel@245
    85
      SYSTIN = 1,
moel@245
    86
      CPUTIN = 2,
moel@245
    87
      AUXTIN = 3,
moel@245
    88
      SMBUSMASTER = 4,
moel@276
    89
      PECI_0 = 5, 
moel@276
    90
      PECI_1 = 6, 
moel@276
    91
      PECI_2 = 7,
moel@276
    92
      PECI_3 = 8,
moel@276
    93
      PECI_4 = 9,
moel@276
    94
      PECI_5 = 10,
moel@276
    95
      PECI_6 = 11,
moel@276
    96
      PECI_7 = 12,
moel@245
    97
      PCH_CHIP_CPU_MAX_TEMP = 13,
moel@245
    98
      PCH_CHIP_TEMP = 14,
moel@245
    99
      PCH_CPU_TEMP = 15,
moel@245
   100
      PCH_MCH_TEMP = 16, 
moel@245
   101
      PCH_DIM0_TEMP = 17,
moel@245
   102
      PCH_DIM1_TEMP = 18,
moel@245
   103
      PCH_DIM2_TEMP = 19,
moel@245
   104
      PCH_DIM3_TEMP = 20
moel@245
   105
    }
moel@245
   106
moel@276
   107
    private enum SourceNCT6776F : byte {
moel@276
   108
      SYSTIN = 1,
moel@276
   109
      CPUTIN = 2,
moel@276
   110
      AUXTIN = 3,
moel@276
   111
      SMBUSMASTER_0 = 4,
moel@276
   112
      SMBUSMASTER_1 = 5,
moel@276
   113
      SMBUSMASTER_2 = 6,
moel@276
   114
      SMBUSMASTER_3 = 7,
moel@276
   115
      SMBUSMASTER_4 = 8,
moel@276
   116
      SMBUSMASTER_5 = 9,
moel@276
   117
      SMBUSMASTER_6 = 10,
moel@276
   118
      SMBUSMASTER_7 = 11,
moel@276
   119
      PECI_0 = 12,
moel@276
   120
      PECI_1 = 13,
moel@276
   121
      PCH_CHIP_CPU_MAX_TEMP = 14,
moel@276
   122
      PCH_CHIP_TEMP = 15,
moel@276
   123
      PCH_CPU_TEMP = 16,
moel@276
   124
      PCH_MCH_TEMP = 17,
moel@276
   125
      PCH_DIM0_TEMP = 18,
moel@276
   126
      PCH_DIM1_TEMP = 19,
moel@276
   127
      PCH_DIM2_TEMP = 20,
moel@276
   128
      PCH_DIM3_TEMP = 21,
moel@276
   129
      BYTE_TEMP = 22
moel@276
   130
    }
moel@276
   131
moel@355
   132
    private enum SourceNCT6779D : byte {
moel@355
   133
      SYSTIN = 1,
moel@355
   134
      CPUTIN = 2,
moel@355
   135
      AUXTIN0 = 3,
moel@355
   136
      AUXTIN1 = 4,
moel@355
   137
      AUXTIN2 = 5,
moel@355
   138
      AUXTIN3 = 6,      
moel@355
   139
      SMBUSMASTER_0 = 8,
moel@355
   140
      SMBUSMASTER_1 = 9,
moel@355
   141
      SMBUSMASTER_2 = 10,
moel@355
   142
      SMBUSMASTER_3 = 11,
moel@355
   143
      SMBUSMASTER_4 = 12,
moel@355
   144
      SMBUSMASTER_5 = 13,
moel@355
   145
      SMBUSMASTER_6 = 14,
moel@355
   146
      SMBUSMASTER_7 = 15,
moel@355
   147
      PECI_0 = 16,
moel@355
   148
      PECI_1 = 17,
moel@355
   149
      PCH_CHIP_CPU_MAX_TEMP = 18,
moel@355
   150
      PCH_CHIP_TEMP = 19,
moel@355
   151
      PCH_CPU_TEMP = 20,
moel@355
   152
      PCH_MCH_TEMP = 21,
moel@355
   153
      PCH_DIM0_TEMP = 22,
moel@355
   154
      PCH_DIM1_TEMP = 23,
moel@355
   155
      PCH_DIM2_TEMP = 24,
moel@355
   156
      PCH_DIM3_TEMP = 25,
moel@355
   157
      BYTE_TEMP = 26
moel@355
   158
    }
moel@355
   159
moel@245
   160
    public NCT677X(Chip chip, byte revision, ushort port) {
moel@245
   161
      this.chip = chip;
moel@245
   162
      this.revision = revision;
moel@245
   163
      this.port = port;
moel@245
   164
moel@245
   165
      if (!IsNuvotonVendor())
moel@265
   166
        return;
moel@265
   167
moel@265
   168
      switch (chip) {
moel@355
   169
        case Chip.NCT6771F:
moel@355
   170
        case Chip.NCT6776F:          
moel@355
   171
          if (chip == Chip.NCT6771F) {
moel@355
   172
            fans = new float?[4];
moel@355
   173
moel@355
   174
            // min value RPM value with 16-bit fan counter
moel@355
   175
            minFanRPM = (int)(1.35e6 / 0xFFFF);
moel@355
   176
moel@355
   177
            temperaturesSource = new byte[] {
moel@355
   178
              (byte)SourceNCT6771F.PECI_0,
moel@355
   179
              (byte)SourceNCT6771F.CPUTIN,
moel@355
   180
              (byte)SourceNCT6771F.AUXTIN,
moel@355
   181
              (byte)SourceNCT6771F.SYSTIN
moel@355
   182
            };            
moel@355
   183
          } else {
moel@355
   184
            fans = new float?[5];
moel@355
   185
moel@355
   186
            // min value RPM value with 13-bit fan counter
moel@355
   187
            minFanRPM = (int)(1.35e6 / 0x1FFF);
moel@355
   188
moel@355
   189
            temperaturesSource = new byte[] {
moel@355
   190
              (byte)SourceNCT6776F.PECI_0,
moel@355
   191
              (byte)SourceNCT6776F.CPUTIN,
moel@355
   192
              (byte)SourceNCT6776F.AUXTIN,
moel@355
   193
              (byte)SourceNCT6776F.SYSTIN 
moel@355
   194
            };
moel@355
   195
          }
moel@355
   196
          fanRpmBaseRegister = 0x656;
moel@355
   197
moel@355
   198
          controls = new float?[3];
moel@355
   199
moel@355
   200
          voltages = new float?[9];
moel@355
   201
          voltageRegisters = new ushort[] 
moel@355
   202
            { 0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x550, 0x551 };
moel@355
   203
          voltageVBatRegister = 0x551;
moel@355
   204
moel@355
   205
          temperatures = new float?[4];
moel@355
   206
          temperatureRegister = new ushort[]
moel@355
   207
            { 0x027, 0x073, 0x075, 0x077, 0x150, 0x250, 0x62B, 0x62C, 0x62D };
moel@355
   208
          temperatureHalfRegister = new ushort[]
moel@355
   209
            { 0, 0x074, 0x076, 0x078, 0x151, 0x251, 0x62E, 0x62E, 0x62E };
moel@355
   210
          temperatureHalfBit = new int[] 
moel@355
   211
            { -1, 7, 7, 7, 7, 7, 0, 1, 2 };
moel@355
   212
          temperatureSourceRegister = new ushort[]
moel@355
   213
            { 0x621, 0x100, 0x200, 0x300, 0x622, 0x623, 0x624, 0x625, 0x626 };
moel@355
   214
moel@355
   215
          alternateTemperatureRegister = new ushort?[] 
moel@355
   216
            { null, null, null, null };
moel@265
   217
          break;
moel@356
   218
moel@355
   219
        case Chip.NCT6779D:
moel@265
   220
          fans = new float?[5];
moel@355
   221
          fanRpmBaseRegister = 0x4C0;
moel@355
   222
moel@265
   223
          // min value RPM value with 13-bit fan counter
moel@265
   224
          minFanRPM = (int)(1.35e6 / 0x1FFF);
moel@355
   225
moel@355
   226
          controls = new float?[5];
moel@355
   227
moel@355
   228
          voltages = new float?[15];
moel@355
   229
          voltageRegisters = new ushort[] 
moel@355
   230
            { 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, 0x488, 
moel@355
   231
              0x489, 0x48A, 0x48B, 0x48C, 0x48D, 0x48E };
moel@355
   232
          voltageVBatRegister = 0x488;
moel@355
   233
moel@355
   234
          temperatures = new float?[7];
moel@355
   235
          temperaturesSource = new byte[] {
moel@355
   236
            (byte)SourceNCT6779D.PECI_0,
moel@355
   237
            (byte)SourceNCT6779D.CPUTIN,
moel@355
   238
            (byte)SourceNCT6779D.SYSTIN,
moel@355
   239
            (byte)SourceNCT6779D.AUXTIN0,
moel@355
   240
            (byte)SourceNCT6779D.AUXTIN1,
moel@355
   241
            (byte)SourceNCT6779D.AUXTIN2,
moel@355
   242
            (byte)SourceNCT6779D.AUXTIN3
moel@355
   243
          };
moel@355
   244
moel@355
   245
          temperatureRegister = new ushort[]
moel@355
   246
            { 0x027, 0x073, 0x075, 0x077, 0x079, 0x07B, 0x150 };
moel@355
   247
          temperatureHalfRegister = new ushort[]
moel@355
   248
            { 0, 0x074, 0x076, 0x078, 0x07A, 0x07C, 0x151 };              
moel@355
   249
          temperatureHalfBit = new int[]
moel@355
   250
            { -1, 7, 7, 7, 7, 7, 7 };
moel@355
   251
          temperatureSourceRegister = new ushort[] 
moel@355
   252
            { 0x621, 0x100, 0x200, 0x300, 0x800, 0x900, 0x622 };
moel@355
   253
moel@355
   254
          alternateTemperatureRegister = new ushort?[] 
moel@355
   255
            {null, 0x491, 0x490, 0x492, 0x493, 0x494, 0x495 };
moel@355
   256
moel@265
   257
          break;        
moel@265
   258
      }
moel@245
   259
    }
moel@245
   260
moel@245
   261
    private bool IsNuvotonVendor() {
moel@245
   262
      return ((ReadByte(VENDOR_ID_HIGH_REGISTER) << 8) |
moel@245
   263
        ReadByte(VENDOR_ID_LOW_REGISTER)) == NUVOTON_VENDOR_ID;
moel@245
   264
    }
moel@245
   265
moel@245
   266
    public byte? ReadGPIO(int index) {
moel@245
   267
      return null;
moel@245
   268
    }
moel@245
   269
moel@245
   270
    public void WriteGPIO(int index, byte value) { }
moel@245
   271
moel@323
   272
moel@323
   273
    private void SaveDefaultFanControl(int index) {
moel@323
   274
      if (!restoreDefaultFanControlRequired[index]) {
moel@323
   275
        initialFanControlMode[index] = ReadByte(FAN_CONTROL_MODE_REG[index]);
moel@323
   276
        initialFanPwmCommand[index] = ReadByte(FAN_PWM_COMMAND_REG[index]);
moel@323
   277
        restoreDefaultFanControlRequired[index] = true;
moel@323
   278
      }
moel@323
   279
    }
moel@323
   280
moel@323
   281
    private void RestoreDefaultFanControl(int index) {
moel@323
   282
      if (restoreDefaultFanControlRequired[index]) {
moel@323
   283
        WriteByte(FAN_CONTROL_MODE_REG[index], initialFanControlMode[index]);
moel@323
   284
        WriteByte(FAN_PWM_COMMAND_REG[index], initialFanPwmCommand[index]);
moel@323
   285
        restoreDefaultFanControlRequired[index] = false;
moel@323
   286
      }
moel@323
   287
    }
moel@323
   288
moel@323
   289
    public void SetControl(int index, byte? value) {
moel@355
   290
      if (index < 0 || index >= controls.Length)
moel@355
   291
        throw new ArgumentOutOfRangeException("index");
moel@355
   292
moel@323
   293
      if (!Ring0.WaitIsaBusMutex(10))
moel@323
   294
        return;
moel@323
   295
moel@323
   296
      if (value.HasValue) {
moel@323
   297
        SaveDefaultFanControl(index);
moel@323
   298
moel@323
   299
        // set manual mode
moel@323
   300
        WriteByte(FAN_CONTROL_MODE_REG[index], 0);
moel@323
   301
moel@323
   302
        // set output value
moel@323
   303
        WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);  
moel@323
   304
      } else {
moel@323
   305
        RestoreDefaultFanControl(index);
moel@323
   306
      }
moel@323
   307
moel@323
   308
      Ring0.ReleaseIsaBusMutex();
moel@323
   309
    }   
moel@323
   310
moel@245
   311
    public Chip Chip { get { return chip; } }
moel@245
   312
    public float?[] Voltages { get { return voltages; } }
moel@245
   313
    public float?[] Temperatures { get { return temperatures; } }
moel@245
   314
    public float?[] Fans { get { return fans; } }
moel@323
   315
    public float?[] Controls { get { return controls; } }
moel@245
   316
moel@245
   317
    public void Update() {
moel@245
   318
      if (!Ring0.WaitIsaBusMutex(10))
moel@245
   319
        return;
moel@245
   320
moel@245
   321
      for (int i = 0; i < voltages.Length; i++) {
moel@355
   322
        float value = 0.008f * ReadByte(voltageRegisters[i]);
moel@245
   323
        bool valid = value > 0;
moel@245
   324
moel@245
   325
        // check if battery voltage monitor is enabled
moel@355
   326
        if (valid && voltageRegisters[i] == voltageVBatRegister) 
moel@245
   327
          valid = (ReadByte(0x005D) & 0x01) > 0;
moel@245
   328
moel@245
   329
        voltages[i] = valid ? value : (float?)null;
moel@245
   330
      }
moel@245
   331
moel@355
   332
      int temperatureSourceMask = 0;
moel@355
   333
      for (int i = temperatureRegister.Length - 1; i >= 0 ; i--) {
moel@355
   334
        int value = ((sbyte)ReadByte(temperatureRegister[i])) << 1;
moel@355
   335
        if (temperatureHalfBit[i] > 0) {
moel@355
   336
          value |= ((ReadByte(temperatureHalfRegister[i]) >>
moel@355
   337
            temperatureHalfBit[i]) & 0x1);
moel@245
   338
        }
moel@245
   339
moel@355
   340
        byte source = ReadByte(temperatureSourceRegister[i]);
moel@355
   341
        temperatureSourceMask |= 1 << source;
moel@245
   342
moel@246
   343
        float? temperature = 0.5f * value;
moel@246
   344
        if (temperature > 125 || temperature < -55)
moel@246
   345
          temperature = null;
moel@246
   346
moel@355
   347
        for (int j = 0; j < temperatures.Length; j++) 
moel@355
   348
          if (temperaturesSource[j] == source)
moel@355
   349
            temperatures[j] = temperature; 
moel@355
   350
      }
moel@355
   351
      for (int i = 0; i < alternateTemperatureRegister.Length; i++) {
moel@355
   352
        if (!alternateTemperatureRegister[i].HasValue)
moel@355
   353
          continue;
moel@355
   354
moel@355
   355
        if ((temperatureSourceMask & (1 << temperaturesSource[i])) > 0)
moel@355
   356
          continue;
moel@355
   357
moel@356
   358
        float? temperature = (sbyte)
moel@356
   359
          ReadByte(alternateTemperatureRegister[i].Value);
moel@356
   360
moel@356
   361
        if (temperature > 125 || temperature < -55)
moel@356
   362
          temperature = null;
moel@356
   363
moel@356
   364
        temperatures[i] = temperature;
moel@245
   365
      }
moel@245
   366
moel@245
   367
      for (int i = 0; i < fans.Length; i++) {
moel@355
   368
        byte high = ReadByte((ushort)(fanRpmBaseRegister + (i << 1)));
moel@355
   369
        byte low = ReadByte((ushort)(fanRpmBaseRegister + (i << 1) + 1));
moel@265
   370
        int value = (high << 8) | low;
moel@265
   371
moel@265
   372
        fans[i] = value > minFanRPM ? value : 0;
moel@245
   373
      }
moel@245
   374
moel@323
   375
      for (int i = 0; i < controls.Length; i++) {
moel@323
   376
        int value = ReadByte(FAN_PWM_OUT_REG[i]);
moel@323
   377
        controls[i] = value / 2.55f;
moel@323
   378
      }
moel@323
   379
moel@245
   380
      Ring0.ReleaseIsaBusMutex();
moel@245
   381
    }
moel@245
   382
moel@245
   383
    public string GetReport() {
moel@245
   384
      StringBuilder r = new StringBuilder();
moel@245
   385
moel@245
   386
      r.AppendLine("LPC " + this.GetType().Name);
moel@245
   387
      r.AppendLine();
moel@245
   388
      r.Append("Chip ID: 0x"); r.AppendLine(chip.ToString("X"));
moel@245
   389
      r.Append("Chip revision: 0x");
moel@245
   390
      r.AppendLine(revision.ToString("X", CultureInfo.InvariantCulture));
moel@245
   391
      r.Append("Base Adress: 0x");
moel@245
   392
      r.AppendLine(port.ToString("X4", CultureInfo.InvariantCulture));
moel@245
   393
      r.AppendLine();
moel@245
   394
moel@245
   395
      if (!Ring0.WaitIsaBusMutex(100))
moel@245
   396
        return r.ToString();
moel@245
   397
moel@246
   398
      ushort[] addresses = new ushort[] { 
moel@246
   399
        0x000, 0x010, 0x020, 0x030, 0x040, 0x050, 0x060, 0x070,
moel@246
   400
        0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 
moel@246
   401
        0x200,        0x220, 0x230, 0x240, 0x250,
moel@246
   402
        0x300,        0x320, 0x330, 0x340, 
moel@355
   403
        0x400, 0x410, 0x420,        0x440, 0x450, 0x460, 0x480, 0x490, 0x4C0,
moel@246
   404
        0x500,                             0x550, 
moel@246
   405
        0x600, 0x610 ,0x620, 0x630, 0x640, 0x650, 0x660, 0x670, 
moel@355
   406
        0x800,
moel@355
   407
        0x900,
moel@246
   408
        0xA00, 0xA10, 0xA20, 0xA30,        0xA50, 0xA60, 0xA70, 
moel@246
   409
        0xB00, 0xB10, 0xB20, 0xB30,        0xB50, 0xB60, 0xB70, 
moel@246
   410
        0xC00, 0xC10, 0xC20, 0xC30,        0xC50, 0xC60, 0xC70,
moel@246
   411
        0xD00, 0xD10, 0xD20, 0xD30,        0xD50, 0xD60, 
moel@246
   412
        0xE00, 0xE10, 0xE20, 0xE30, 
moel@246
   413
        0xF00, 0xF10, 0xF20, 0xF30};
moel@246
   414
moel@245
   415
      r.AppendLine("Hardware Monitor Registers");
moel@245
   416
      r.AppendLine();
moel@245
   417
      r.AppendLine("       00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
moel@245
   418
      r.AppendLine();
moel@246
   419
      foreach (ushort address in addresses) {
moel@245
   420
          r.Append(" ");
moel@246
   421
          r.Append(address.ToString("X3", CultureInfo.InvariantCulture));
moel@245
   422
          r.Append("  ");
moel@246
   423
          for (ushort j = 0; j <= 0xF; j++) {
moel@245
   424
            r.Append(" ");
moel@246
   425
            r.Append(ReadByte((ushort)(address | j)).ToString(
moel@245
   426
              "X2", CultureInfo.InvariantCulture));
moel@245
   427
          }
moel@245
   428
          r.AppendLine();
moel@245
   429
      }
moel@245
   430
      r.AppendLine();
moel@245
   431
moel@245
   432
      Ring0.ReleaseIsaBusMutex();
moel@245
   433
moel@245
   434
      return r.ToString();
moel@245
   435
    }
moel@245
   436
  }
moel@245
   437
}