Settings.pm 13.7 KB
Newer Older
1
## @file
2

3
## @class FIJI::Settings
4
#
5
# Contains helper functions to deal with FIJI Settings files.
6
7
package FIJI::Settings;

8
9
10
use strict;
use warnings;

11
use Scalar::Util 'blessed';
12
13
14
15
use Log::Log4perl qw(get_logger);
use Scalar::Util "looks_like_number";
use Config::Simple;

16
17
use FIJI qw(:all);

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
sub new ($;$$) {
  my $logger = get_logger();
  my ($class, $fiji_ini_file, $existing_settings) = @_;

  my $settings_ref;
  if (defined($fiji_ini_file)) {
    $settings_ref = read_settingsfile($fiji_ini_file, $existing_settings);
    if (!defined($settings_ref)) {
      return undef;
    }
    if (defined($existing_settings)) {
      return $existing_settings;
    }
  } else {
    # Without any config file as input, simply create an empty settings
    # hash with default design constants.
    my $consts_ref = {};
    my $ret = _set_defaults(DESIGNMAP, $consts_ref);
    if (!defined($ret)) {
      $logger->error("Could not set defaults for design constants.");
      return undef;
    }
    $settings_ref = { 'design' => $consts_ref };
  }
  my $r = bless($settings_ref, $class);
  if (!ref($r) || !UNIVERSAL::can($r,'can')) {
    $logger->error("Could not bless FIJI::Settings class from \"$fiji_ini_file\".");
    return undef;
  }
  return $r;
}

## @method save ($fiji_ini_file)
# @brief Store contained FIJI Settings to file.
#
# @ATTENTION Will happily overwrite existing files!
#
# \param fiji_ini_file The file name to write the FIJI Settings to.
sub save ($$) {
  my $logger = get_logger();
  my ($self, $fiji_ini_file) = @_;
  return "No file name given" if !defined($fiji_ini_file);

  my $fiji_ini = new Config::Simple(syntax=>'ini');
62
63
  my $design_ref;
  my $fiu_cnt = 0;
64
65
66
67
68
69
  foreach my $key (keys %{$self}) {
    my $val = $self->{$key};
    $logger->debug(sprintf("Key: %s, type: %s, value: %s", $key, ref(\$val), $val));
    if (ref(\$val) eq "REF") {
      if (ref($val) eq "HASH") {
        if ($key eq "design") {
70
          $design_ref = $val;
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
          next;
        }
      } elsif (ref($val) eq "ARRAY") {
        if ($key eq "FIUs") {
          foreach my $fiu (@{$val}) {
            my $ini_fiu;
            foreach my $k (keys $fiu) {
              $ini_fiu->{FIUMAP->{$k}->{'ini_name'}} = $fiu->{$k};
            }
            $fiji_ini->set_block("FIU" . $fiu_cnt++, $ini_fiu);
          }
          next;
        }
      }
    }
    my $err = "Unknown element found in FIJI Settings: \"$val\"";
    $logger->error($err);
    return $err;
  }
90
91
92
  $design_ref->{'FIU_NUM'} = $fiu_cnt;
  $fiji_ini->set_block("CONSTS", $design_ref);

93
94
95
96
97
98
99
100
101
102
  if (!defined($fiji_ini->write($fiji_ini_file))) {
    my $err = Config::Simple->error();
    $logger->error($err);
    return $err;
  }
  return undef;
}


## @function read_settingsfile ($fiji_ini_file)
103
# @brief Load the FIJI Settings file containing design and FIU constants.
104
#
105
106
107
108
109
110
# \param fiji_ini_file The name of an .ini file with FIJI Settings:
#         - a 'consts' block containing the constants specified by
#           \ref _sanitize_consts.
#         - at least one FIU block named "FIU<number>" where "<number>"
#           is a strictly increasing integer starting with 0 containing
#           the constants for the respective FIU, see \ref _sanitize_fiu
111
112
#
# \returns a reference to the hash containing the read constants.
113
sub read_settingsfile {
114
  my $logger = get_logger();
115
  my ($fiji_ini_file, $existing_settings) = @_;
116
117
118
  my $fiji_ini;
  eval { $fiji_ini = new Config::Simple($fiji_ini_file) }; # pesky library tries to die on syntax errors
  if (!defined($fiji_ini)) {
119
    $logger->error("Could not read config file \"$fiji_ini_file\": " . (defined($@) ? $@ : Config::Simple->error()));
120
121
122
    return undef;
  }

123
  my $fiji_consts = $fiji_ini->get_block("CONSTS");
124
  if (!(%$fiji_consts)) {
125
    $logger->error("Could not fetch CONSTS block from config file \"$fiji_ini_file\"");
126
127
    return undef;
  }
128
129
  $fiji_consts = _sanitize_consts($fiji_consts);
  if (!defined($fiji_consts)) {
130
    $logger->error("Design constants in FIJI Settings invalid");
131
132
    return undef;
  }
133
  my $fiji_settings_ref;
134
  if (defined($existing_settings)) {
135
136
137
138
139
140
141
142
143
    $fiji_settings_ref = $existing_settings;
    if (!blessed($fiji_settings_ref) || !$fiji_settings_ref->isa("FIJI::Settings")) {
      $logger->error("Given settings are not of type FIJI::Settings.");
      return undef;
    }
    $fiji_settings_ref->{'design'} = {};
    $fiji_settings_ref->{'FIUs'} = ();
  }
  $fiji_settings_ref->{'design'} = $fiji_consts;
144
145
146
147
148
149
150
151
152
153

  my $fiu_num = 0;
  while (1) {
    my $fiu_name = "FIU" . $fiu_num;
    my $fiji_fiu_cfg = $fiji_ini->get_block($fiu_name);
    if (!(%$fiji_fiu_cfg)) {
      last;
    }
    my $fiji_fiu = _sanitize_fiu($fiji_fiu_cfg);
    if (!defined($fiji_fiu)) {
154
      $logger->error("Constants for $fiu_name in FIJI Settings are invalid");
155
156
      return undef;
    }
157
    push(@{$fiji_settings_ref->{'FIUs'}}, $fiji_fiu);
158
159
160
161
162
    $fiu_num++;
    $logger->trace("Read in $fiu_name from FIJI Settings file successfully.");
  }

  if ($fiu_num == 0) {
163
164
    $logger->debug("Could not fetch any FIU block from config file \"$fiji_ini_file\"");
    $fiji_settings_ref->{'FIUs'} = [];
165
  }
166
167
168
169

  # FIU_NUM is optional in the Settings file... if it was set check that
  # it corresponds to the number of FIU<number> blocks.
  if (defined($fiji_consts->{'FIU_NUM'}) && $fiji_consts->{'FIU_NUM'} != $fiu_num) {
170
      $logger->error(FIU_NUM->{'ini_name'} . " does not match the numbers of FIU blocks found.");
171
172
173
174
175
176
      return undef;
  } else {
    $fiji_consts->{'FIU_NUM'} = $fiu_num; # assume the best if FIU_NUM constant is not given
  }

  $logger->info("Successfully read in design constants and $fiu_num FIU definitions from FIJI Settings file.");
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
  return $fiji_settings_ref;
}

## @function set_fiu_defaults (%$fiu_ref)
# @brief Overwrite existing fields (if defined) with defaults defined in FIJI.pm.
#
# FIXME: almost 1:1 copy from _set_defaults
sub set_fiu_defaults ($) {
  my $logger = get_logger();
  my ($self, $consts_ref) = @_;
  my $map_ref = FIUMAP;
  foreach my $k (keys($map_ref)) {
    my $ini_name = $map_ref->{$k}->{'ini_name'};
    if (exists($map_ref->{$k}->{'default'})) {
      $consts_ref->{$k} = $map_ref->{$k}->{default};
      # If the default key is there but its value is undef then
      # the value will be set somewhere else later (used for e.g. FIU_NUM)
      if (!defined($consts_ref->{$k})) {
        next;
      }
      $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $ini_name, $map_ref->{$k}->{default}));
    } elsif ($map_ref->{$k}->{'not_supplied'}) {
       $consts_ref->{$k} = undef;
    }
  }
202
203
}

204
205
206
207
208
209
210
211
## @function _set_defaults (%$map_ref, %$consts_ref)
# @brief Set defaults according to FIJI.pm.
#
# The functions compares the given constants to the defaults and rules
# in FIJI.pm.
sub _set_defaults {
  my $logger = get_logger();
  my ($map_ref, $consts_ref) = @_;
212
  # my $new_hash = {};
213
214
215
216
217

  # Iterating over respective hash from FIJI.pm and set defaults if need be
  foreach my $k (keys($map_ref)) {
    my $ini_name = $map_ref->{$k}->{'ini_name'};
    if (exists($consts_ref->{$ini_name})) {
218
219
220
221
222
      if ($ini_name ne $k) {
        $consts_ref->{$k} = $consts_ref->{$ini_name};
        $logger->trace(sprintf("Copying setting %s (%s) = %s.", $k, $ini_name, defined($consts_ref->{$ini_name}) ? $consts_ref->{$ini_name} : "undef"));
        delete $consts_ref->{$ini_name};
      }
223
    } else {
224
225
      if (exists($map_ref->{$k}->{'default'})) {
        $consts_ref->{$k} = $map_ref->{$k}->{default};
226
        # If the default key is there but its value is undef then
227
        # the value will be set somewhere else later (used for e.g. FIU_NUM)
228
        if (!defined($consts_ref->{$k})) {
229
230
          next;
        }
231
232
233
        $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $ini_name, $map_ref->{$k}->{default}));
      } elsif ($map_ref->{$k}->{'not_supplied'}) {
        next;
234
235
236
237
238
239
      } else {
        $logger->error(sprintf("%s is missing from FIJI Settings.", $ini_name));
        return undef;
      }
    }

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
    if (defined($map_ref->{$k}->{'type'})) {
      my $orig = $consts_ref->{$k};
      if ($map_ref->{$k}->{'type'} eq 'numeric') {
        # convert non-decimal (hexadecimal, binary, octal) values to decimal
        $consts_ref->{$k} = oct($orig) if $orig =~ /^0/;
        if (!looks_like_number($consts_ref->{$k})) {
          $logger->error("$orig does not look like a number.");
          return undef;
        }
      } elsif ($map_ref->{$k}->{'type'} eq 'boolean') {
        # convert strings to binary if need be
        if (!defined($orig)) {
          $logger->error("\"undef\" is not a boolean value.");
          return undef;
        } elsif (lc($orig) eq 'true') {
          $orig = 1;
        } elsif (lc($orig) eq 'false') {
          $orig = 0;
        }
        if (($orig ne '0') && ($orig ne '1')) {
          $logger->error("\"$orig\" does not look like a boolean value.");
          return undef;
        }
        # ensure proper boolean value, i.e. 0 or 1
        $consts_ref->{$k} = (!!$orig) ? 1 : 0;
265
      }
266
      $logger->trace("Converted value of $k (\"$orig\") to \"$consts_ref->{$k}\".") if ($orig ne $consts_ref->{$k});
267
    } elsif (defined($map_ref->{$k}->{'values'})) {
268
269
      if (!grep {$_ eq $consts_ref->{$k}} @{$map_ref->{$k}->{'values'}}) {
        $logger->error("$consts_ref->{$k} is not allowed. Allowed values are: " . join(", ", @{$map_ref->{$k}->{'values'}}));
270
271
        return undef;
      }
272
273
    }
  }
274
  return $consts_ref;
275
276
277
}

## @function _sanitize_fiu (%$fiu_ref)
278
279
# @brief Convert and sanity check FIJI Settings.
#
280
281
282
283
284
285
286
287
288
289
290
291
292
# \param fiu_ref a reference to a hash containing FIJI Settings for a
#                single FIU.
#
# \returns A new hash with all constants required in the FIU settings
#          in sanitized form, or undef on errors.
sub _sanitize_fiu {
  my $logger = get_logger();
  my ($fiu_ref) = @_;
  if (ref($fiu_ref) ne 'HASH') {
    $logger->error("Parameter is not a reference to a hash (containing FIU constants).");
    return undef;
  }

293
294
295
  my $ret = _set_defaults(FIUMAP, $fiu_ref);
  if (!defined($ret)) {
    $logger->error("Could not set defaults for design constants.");
296
    return undef;
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
  }

  # check for sane values
  # if (($new_hash->{FIU_CFG_BITS} <= 0)) {
      # $logger->error("FIU_CFG_BITS is <= 0.");
      # return undef;
  # }

  return $fiu_ref;
}

## @function _sanitize_consts (%$consts_ref)
# @brief Convert and sanity check FIJI Settings for the whole design.
#
# NOTE: The function returns a reference to a *new* hashmap that contains
#       only the design constants!
#
# This function takes a hash of design settings and converts it to the
315
316
317
318
319
320
321
322
# respective internal representation. This allows to use different
# names in the external file than within the implementation.
#
# First, this function sets some default values for missing constants if
# there is a default stored for the respective constant in FIJI.pm.
# If there is no default available it has to be a mandatory value and
# hence the function returns non-0 in that case.
#
323
324
325
326
327
328
# ARM_DURATION_WIDTH and INJECT_DURATION_WIDTH are handled differently
# because they are not official FIJI Settings because there is only a
# single timer (yet). However, the Perl implementation supports
# independent timer widths named as above. If they are not given in
# consts_ref then the value of TIMER_WIDTH is used. If that is not given
# either the default value for TIMER_WIDTH is used.
329
330
331
#
# The second part of the function deals with sanity checks for the values
# themselves. It checks for the following conditions:
332
333
334
#
#   - FIU_NUM: > 0
#   - FIU_CFG_BITS: > 0
335
#   - ARM_DURATION_WIDTH, INJECT_DURATION_WIDTH: > 0, multiple of 8
336
337
338
#   - ID: > 0, < 2^15-1
#   - BAUDRATE: > 0
#
339
# \param consts_ref a reference to a hash containing some design settings
340
#
341
342
# \returns A new hash with all constants required in the design settings
#          in sanitized form, or undef on errors.
343
344
345
346
sub _sanitize_consts {
  my $logger = get_logger();
  my ($consts_ref) = @_;
  if (ref($consts_ref) ne 'HASH') {
347
    $logger->error("Parameter is not a reference to a hash (containing design constants).");
348
349
350
351
352
353
    return undef;
  }

  # Special cases of defaults handled separately here.
  # Set ARM_DURATION_WIDTH and INJECT_DURATION_WIDTH to TIMER_WIDTH (or its default)
  foreach my $k ('ARM_DURATION_WIDTH', 'INJECT_DURATION_WIDTH') {
354
    if (!exists($consts_ref->{DESIGNMAP->{$k}->{'ini_name'}})) {
355
      if (exists($consts_ref->{'TIMER_WIDTH'})) {
356
357
        $consts_ref->{DESIGNMAP->{$k}->{'ini_name'}} = $consts_ref->{'TIMER_WIDTH'};
        $logger->trace(sprintf("Using TIMER_WIDTH value as default for %s (%s).", DESIGNMAP->{$k}->{'ini_name'}, $consts_ref->{'TIMER_WIDTH'}));
358
      } else {
359
360
        $consts_ref->{DESIGNMAP->{$k}->{'ini_name'}} = TIMER_WIDTH->{'default'};
        $logger->trace(sprintf("Using TIMER_WIDTH default as default for %s (%s).", DESIGNMAP->{$k}->{'ini_name'}, TIMER_WIDTH->{default}));
361
362
      }
    }
363
364
  }

365
366
367
  my $ret = _set_defaults(DESIGNMAP, $consts_ref);
  if (!defined($ret)) {
    $logger->error("Could not set defaults for design constants.");
368
    return undef;
369
370
  }

371
  # check for sane values
372
  if (($consts_ref->{FIU_CFG_BITS} <= 0)) {
373
      $logger->error("FIU_CFG_BITS is <= 0.");
374
      return undef;
375
  }
376
377
  if (($consts_ref->{ARM_DURATION_WIDTH} <= 0) || ($consts_ref->{ARM_DURATION_WIDTH} % 8 != 0)) {
      $logger->error("ARM_DURATION_WIDTH is invalid ($consts_ref->{ARM_DURATION_WIDTH}).");
378
      return undef;
379
  }
380
381
  if (($consts_ref->{INJECT_DURATION_WIDTH} <= 0) || (($consts_ref->{INJECT_DURATION_WIDTH} % 8) != 0)) {
      $logger->error("INJECT_DURATION_WIDTH is invalid ($consts_ref->{INJECT_DURATION_WIDTH}).");
382
      return undef;
383
  }
384
385
  if (defined($consts_ref->{ID}) && ($consts_ref->{ID} <= 0 || $consts_ref->{ID} > (2**15 - 1))) {
      $logger->error("ID is invalid ($consts_ref->{ID}).");
386
      return undef;
387
  }
388
  if (($consts_ref->{BAUDRATE} <= 0)) {
389
      $logger->error("BAUDRATE missing is <= 0.");
390
      return undef;
391
  }
392
  return $consts_ref;
393
394
395
}

1;