Hardware/LPC/NCT677X.cs
author moel.mich
Sun, 01 Jul 2012 21:44:07 +0000
changeset 355 17dbf781401e
parent 344 3145aadca3d2
child 356 e471b1c2b1a6
permissions -rw-r--r--
Added experimental support for Nuvoton NCT6779D super I/O chips.
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@246
    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@355
   218
        case Chip.NCT6779D:
moel@265
   219
          fans = new float?[5];
moel@355
   220
          fanRpmBaseRegister = 0x4C0;
moel@355
   221
moel@265
   222
          // min value RPM value with 13-bit fan counter
moel@265
   223
          minFanRPM = (int)(1.35e6 / 0x1FFF);
moel@355
   224
moel@355
   225
          controls = new float?[5];
moel@355
   226
moel@355
   227
          voltages = new float?[15];
moel@355
   228
          voltageRegisters = new ushort[] 
moel@355
   229
            { 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, 0x488, 
moel@355
   230
              0x489, 0x48A, 0x48B, 0x48C, 0x48D, 0x48E };
moel@355
   231
          voltageVBatRegister = 0x488;
moel@355
   232
moel@355
   233
          temperatures = new float?[7];
moel@355
   234
          temperaturesSource = new byte[] {
moel@355
   235
            (byte)SourceNCT6779D.PECI_0,
moel@355
   236
            (byte)SourceNCT6779D.CPUTIN,
moel@355
   237
            (byte)SourceNCT6779D.SYSTIN,
moel@355
   238
            (byte)SourceNCT6779D.AUXTIN0,
moel@355
   239
            (byte)SourceNCT6779D.AUXTIN1,
moel@355
   240
            (byte)SourceNCT6779D.AUXTIN2,
moel@355
   241
            (byte)SourceNCT6779D.AUXTIN3
moel@355
   242
          };
moel@355
   243
moel@355
   244
          temperatureRegister = new ushort[]
moel@355
   245
            { 0x027, 0x073, 0x075, 0x077, 0x079, 0x07B, 0x150 };
moel@355
   246
          temperatureHalfRegister = new ushort[]
moel@355
   247
            { 0, 0x074, 0x076, 0x078, 0x07A, 0x07C, 0x151 };              
moel@355
   248
          temperatureHalfBit = new int[]
moel@355
   249
            { -1, 7, 7, 7, 7, 7, 7 };
moel@355
   250
          temperatureSourceRegister = new ushort[] 
moel@355
   251
            { 0x621, 0x100, 0x200, 0x300, 0x800, 0x900, 0x622 };
moel@355
   252
moel@355
   253
          alternateTemperatureRegister = new ushort?[] 
moel@355
   254
            {null, 0x491, 0x490, 0x492, 0x493, 0x494, 0x495 };
moel@355
   255
moel@265
   256
          break;        
moel@265
   257
      }
moel@245
   258
    }
moel@245
   259
moel@245
   260
    private bool IsNuvotonVendor() {
moel@245
   261
      return ((ReadByte(VENDOR_ID_HIGH_REGISTER) << 8) |
moel@245
   262
        ReadByte(VENDOR_ID_LOW_REGISTER)) == NUVOTON_VENDOR_ID;
moel@245
   263
    }
moel@245
   264
moel@245
   265
    public byte? ReadGPIO(int index) {
moel@245
   266
      return null;
moel@245
   267
    }
moel@245
   268
moel@245
   269
    public void WriteGPIO(int index, byte value) { }
moel@245
   270
moel@323
   271
moel@323
   272
    private void SaveDefaultFanControl(int index) {
moel@323
   273
      if (!restoreDefaultFanControlRequired[index]) {
moel@323
   274
        initialFanControlMode[index] = ReadByte(FAN_CONTROL_MODE_REG[index]);
moel@323
   275
        initialFanPwmCommand[index] = ReadByte(FAN_PWM_COMMAND_REG[index]);
moel@323
   276
        restoreDefaultFanControlRequired[index] = true;
moel@323
   277
      }
moel@323
   278
    }
moel@323
   279
moel@323
   280
    private void RestoreDefaultFanControl(int index) {
moel@323
   281
      if (restoreDefaultFanControlRequired[index]) {
moel@323
   282
        WriteByte(FAN_CONTROL_MODE_REG[index], initialFanControlMode[index]);
moel@323
   283
        WriteByte(FAN_PWM_COMMAND_REG[index], initialFanPwmCommand[index]);
moel@323
   284
        restoreDefaultFanControlRequired[index] = false;
moel@323
   285
      }
moel@323
   286
    }
moel@323
   287
moel@323
   288
    public void SetControl(int index, byte? value) {
moel@355
   289
      if (index < 0 || index >= controls.Length)
moel@355
   290
        throw new ArgumentOutOfRangeException("index");
moel@355
   291
moel@323
   292
      if (!Ring0.WaitIsaBusMutex(10))
moel@323
   293
        return;
moel@323
   294
moel@323
   295
      if (value.HasValue) {
moel@323
   296
        SaveDefaultFanControl(index);
moel@323
   297
moel@323
   298
        // set manual mode
moel@323
   299
        WriteByte(FAN_CONTROL_MODE_REG[index], 0);
moel@323
   300
moel@323
   301
        // set output value
moel@323
   302
        WriteByte(FAN_PWM_COMMAND_REG[index], value.Value);  
moel@323
   303
      } else {
moel@323
   304
        RestoreDefaultFanControl(index);
moel@323
   305
      }
moel@323
   306
moel@323
   307
      Ring0.ReleaseIsaBusMutex();
moel@323
   308
    }   
moel@323
   309
moel@245
   310
    public Chip Chip { get { return chip; } }
moel@245
   311
    public float?[] Voltages { get { return voltages; } }
moel@245
   312
    public float?[] Temperatures { get { return temperatures; } }
moel@245
   313
    public float?[] Fans { get { return fans; } }
moel@323
   314
    public float?[] Controls { get { return controls; } }
moel@245
   315
moel@245
   316
    public void Update() {
moel@245
   317
      if (!Ring0.WaitIsaBusMutex(10))
moel@245
   318
        return;
moel@245
   319
moel@245
   320
      for (int i = 0; i < voltages.Length; i++) {
moel@355
   321
        float value = 0.008f * ReadByte(voltageRegisters[i]);
moel@245
   322
        bool valid = value > 0;
moel@245
   323
moel@245
   324
        // check if battery voltage monitor is enabled
moel@355
   325
        if (valid && voltageRegisters[i] == voltageVBatRegister) 
moel@245
   326
          valid = (ReadByte(0x005D) & 0x01) > 0;
moel@245
   327
moel@245
   328
        voltages[i] = valid ? value : (float?)null;
moel@245
   329
      }
moel@245
   330
moel@355
   331
      int temperatureSourceMask = 0;
moel@355
   332
      for (int i = temperatureRegister.Length - 1; i >= 0 ; i--) {
moel@355
   333
        int value = ((sbyte)ReadByte(temperatureRegister[i])) << 1;
moel@355
   334
        if (temperatureHalfBit[i] > 0) {
moel@355
   335
          value |= ((ReadByte(temperatureHalfRegister[i]) >>
moel@355
   336
            temperatureHalfBit[i]) & 0x1);
moel@245
   337
        }
moel@245
   338
moel@355
   339
        byte source = ReadByte(temperatureSourceRegister[i]);
moel@355
   340
        temperatureSourceMask |= 1 << source;
moel@245
   341
moel@246
   342
        float? temperature = 0.5f * value;
moel@246
   343
        if (temperature > 125 || temperature < -55)
moel@246
   344
          temperature = null;
moel@246
   345
moel@355
   346
        for (int j = 0; j < temperatures.Length; j++) 
moel@355
   347
          if (temperaturesSource[j] == source)
moel@355
   348
            temperatures[j] = temperature; 
moel@355
   349
      }
moel@355
   350
      for (int i = 0; i < alternateTemperatureRegister.Length; i++) {
moel@355
   351
        if (!alternateTemperatureRegister[i].HasValue)
moel@355
   352
          continue;
moel@355
   353
moel@355
   354
        if ((temperatureSourceMask & (1 << temperaturesSource[i])) > 0)
moel@355
   355
          continue;
moel@355
   356
moel@355
   357
        temperatures[i] = ReadByte(alternateTemperatureRegister[i].Value);
moel@245
   358
      }
moel@245
   359
moel@245
   360
      for (int i = 0; i < fans.Length; i++) {
moel@355
   361
        byte high = ReadByte((ushort)(fanRpmBaseRegister + (i << 1)));
moel@355
   362
        byte low = ReadByte((ushort)(fanRpmBaseRegister + (i << 1) + 1));
moel@265
   363
        int value = (high << 8) | low;
moel@265
   364
moel@265
   365
        fans[i] = value > minFanRPM ? value : 0;
moel@245
   366
      }
moel@245
   367
moel@323
   368
      for (int i = 0; i < controls.Length; i++) {
moel@323
   369
        int value = ReadByte(FAN_PWM_OUT_REG[i]);
moel@323
   370
        controls[i] = value / 2.55f;
moel@323
   371
      }
moel@323
   372
moel@245
   373
      Ring0.ReleaseIsaBusMutex();
moel@245
   374
    }
moel@245
   375
moel@245
   376
    public string GetReport() {
moel@245
   377
      StringBuilder r = new StringBuilder();
moel@245
   378
moel@245
   379
      r.AppendLine("LPC " + this.GetType().Name);
moel@245
   380
      r.AppendLine();
moel@245
   381
      r.Append("Chip ID: 0x"); r.AppendLine(chip.ToString("X"));
moel@245
   382
      r.Append("Chip revision: 0x");
moel@245
   383
      r.AppendLine(revision.ToString("X", CultureInfo.InvariantCulture));
moel@245
   384
      r.Append("Base Adress: 0x");
moel@245
   385
      r.AppendLine(port.ToString("X4", CultureInfo.InvariantCulture));
moel@245
   386
      r.AppendLine();
moel@245
   387
moel@245
   388
      if (!Ring0.WaitIsaBusMutex(100))
moel@245
   389
        return r.ToString();
moel@245
   390
moel@246
   391
      ushort[] addresses = new ushort[] { 
moel@246
   392
        0x000, 0x010, 0x020, 0x030, 0x040, 0x050, 0x060, 0x070,
moel@246
   393
        0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 
moel@246
   394
        0x200,        0x220, 0x230, 0x240, 0x250,
moel@246
   395
        0x300,        0x320, 0x330, 0x340, 
moel@355
   396
        0x400, 0x410, 0x420,        0x440, 0x450, 0x460, 0x480, 0x490, 0x4C0,
moel@246
   397
        0x500,                             0x550, 
moel@246
   398
        0x600, 0x610 ,0x620, 0x630, 0x640, 0x650, 0x660, 0x670, 
moel@355
   399
        0x800,
moel@355
   400
        0x900,
moel@246
   401
        0xA00, 0xA10, 0xA20, 0xA30,        0xA50, 0xA60, 0xA70, 
moel@246
   402
        0xB00, 0xB10, 0xB20, 0xB30,        0xB50, 0xB60, 0xB70, 
moel@246
   403
        0xC00, 0xC10, 0xC20, 0xC30,        0xC50, 0xC60, 0xC70,
moel@246
   404
        0xD00, 0xD10, 0xD20, 0xD30,        0xD50, 0xD60, 
moel@246
   405
        0xE00, 0xE10, 0xE20, 0xE30, 
moel@246
   406
        0xF00, 0xF10, 0xF20, 0xF30};
moel@246
   407
moel@245
   408
      r.AppendLine("Hardware Monitor Registers");
moel@245
   409
      r.AppendLine();
moel@245
   410
      r.AppendLine("       00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
moel@245
   411
      r.AppendLine();
moel@246
   412
      foreach (ushort address in addresses) {
moel@245
   413
          r.Append(" ");
moel@246
   414
          r.Append(address.ToString("X3", CultureInfo.InvariantCulture));
moel@245
   415
          r.Append("  ");
moel@246
   416
          for (ushort j = 0; j <= 0xF; j++) {
moel@245
   417
            r.Append(" ");
moel@246
   418
            r.Append(ReadByte((ushort)(address | j)).ToString(
moel@245
   419
              "X2", CultureInfo.InvariantCulture));
moel@245
   420
          }
moel@245
   421
          r.AppendLine();
moel@245
   422
      }
moel@245
   423
      r.AppendLine();
moel@245
   424
moel@245
   425
      Ring0.ReleaseIsaBusMutex();
moel@245
   426
moel@245
   427
      return r.ToString();
moel@245
   428
    }
moel@245
   429
  }
moel@245
   430
}