Hardware/LPC/NCT677X.cs
author moel.mich
Sun, 23 Sep 2012 18:37:43 +0000
changeset 380 573f1fff48b2
parent 355 17dbf781401e
child 413 362c5e77197d
permissions -rw-r--r--
Fixed Issue 387. The new implementation does not try to start a ring 0 driver that already exists, but could not be opened. It tries to delete the driver and install it new. The driver is now stored temporarily in the application folder. The driver is not correctly removed on system shutdown.
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
}