Settings.pm 12.4 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
12
13
14
use Log::Log4perl qw(get_logger);
use Scalar::Util "looks_like_number";
use Config::Simple;

15
16
use FIJI qw(:all);

17
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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');
  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") {
          $fiji_ini->set_block("CONSTS", $val);
          next;
        }
      } elsif (ref($val) eq "ARRAY") {
        if ($key eq "FIUs") {
          my $fiu_cnt = 0;
          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;
  }
  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)
98
# @brief Load the FIJI Settings file containing design and FIU constants.
99
#
100
101
102
103
104
105
# \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
106
107
#
# \returns a reference to the hash containing the read constants.
108
sub read_settingsfile {
109
  my $logger = get_logger();
110
  my ($fiji_ini_file, $existing_settings) = @_;
111
112
113
  my $fiji_ini;
  eval { $fiji_ini = new Config::Simple($fiji_ini_file) }; # pesky library tries to die on syntax errors
  if (!defined($fiji_ini)) {
114
    $logger->error("Could not read config file \"$fiji_ini_file\": " . (defined($@) ? $@ : Config::Simple->error()));
115
116
117
    return undef;
  }

118
  my $fiji_settings = (defined($existing_settings)) ? $existing_settings : {};
119
  my $fiji_consts = $fiji_ini->get_block("CONSTS");
120
  if (!(%$fiji_consts)) {
121
    $logger->error("Could not fetch CONSTS block from config file \"$fiji_ini_file\"");
122
123
    return undef;
  }
124
125
  $fiji_consts = _sanitize_consts($fiji_consts);
  if (!defined($fiji_consts)) {
126
    $logger->error("Design constants in FIJI Settings invalid");
127
128
    return undef;
  }
129
  $fiji_settings->{'design'} = $fiji_consts;
130
131
132
133
134
135
136
137
138
139

  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)) {
140
      $logger->error("Constants for $fiu_name in FIJI Settings are invalid");
141
142
      return undef;
    }
143
    push(@{$fiji_settings->{'FIUs'}}, $fiji_fiu);
144
145
146
147
148
    $fiu_num++;
    $logger->trace("Read in $fiu_name from FIJI Settings file successfully.");
  }

  if ($fiu_num == 0) {
149
    $logger->error("Could not fetch any FIU block from config file \"$fiji_ini_file\"");
150
    return undef;
151
  }
152
153
154
155

  # 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) {
156
      $logger->error(FIU_NUM->{'ini_name'} . " does not match the numbers of FIU blocks found.");
157
158
159
160
161
162
      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.");
163
  return $fiji_settings;
164
165
}

166
167
168
169
170
171
172
173
## @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) = @_;
174
  # my $new_hash = {};
175
176
177
178
179

  # 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})) {
180
181
182
183
184
      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};
      }
185
    } else {
186
187
      if (exists($map_ref->{$k}->{'default'})) {
        $consts_ref->{$k} = $map_ref->{$k}->{default};
188
189
        # If the default key is there but its value is undef then
        # the value will be set somewhere else later (used for FIU_NUM)
190
        if (!defined($consts_ref->{$k})) {
191
192
          next;
        }
193
194
195
        $logger->trace(sprintf("Adding default constant: %s (%s) = %s.", $k, $ini_name, $map_ref->{$k}->{default}));
      } elsif ($map_ref->{$k}->{'not_supplied'}) {
        next;
196
197
198
199
200
201
      } else {
        $logger->error(sprintf("%s is missing from FIJI Settings.", $ini_name));
        return undef;
      }
    }

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
    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;
227
      }
228
      $logger->trace("Converted value of $k (\"$orig\") to \"$consts_ref->{$k}\".") if ($orig ne $consts_ref->{$k});
229
    } elsif (defined($map_ref->{$k}->{'values'})) {
230
231
      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'}}));
232
233
        return undef;
      }
234
235
    }
  }
236
  return $consts_ref;
237
238
239
}

## @function _sanitize_fiu (%$fiu_ref)
240
241
# @brief Convert and sanity check FIJI Settings.
#
242
243
244
245
246
247
248
249
250
251
252
253
254
# \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;
  }

255
256
257
  my $ret = _set_defaults(FIUMAP, $fiu_ref);
  if (!defined($ret)) {
    $logger->error("Could not set defaults for design constants.");
258
    return undef;
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  }

  # 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
277
278
279
280
281
282
283
284
# 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.
#
285
286
287
288
289
290
# 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.
291
292
293
#
# The second part of the function deals with sanity checks for the values
# themselves. It checks for the following conditions:
294
295
296
#
#   - FIU_NUM: > 0
#   - FIU_CFG_BITS: > 0
297
#   - ARM_DURATION_WIDTH, INJECT_DURATION_WIDTH: > 0, multiple of 8
298
299
300
#   - ID: > 0, < 2^15-1
#   - BAUDRATE: > 0
#
301
# \param consts_ref a reference to a hash containing some design settings
302
#
303
304
# \returns A new hash with all constants required in the design settings
#          in sanitized form, or undef on errors.
305
306
307
308
sub _sanitize_consts {
  my $logger = get_logger();
  my ($consts_ref) = @_;
  if (ref($consts_ref) ne 'HASH') {
309
    $logger->error("Parameter is not a reference to a hash (containing design constants).");
310
311
312
313
314
315
    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') {
316
    if (!exists($consts_ref->{DESIGNMAP->{$k}->{'ini_name'}})) {
317
      if (exists($consts_ref->{'TIMER_WIDTH'})) {
318
319
        $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'}));
320
      } else {
321
322
        $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}));
323
324
      }
    }
325
326
  }

327
328
329
  my $ret = _set_defaults(DESIGNMAP, $consts_ref);
  if (!defined($ret)) {
    $logger->error("Could not set defaults for design constants.");
330
    return undef;
331
332
  }

333
  # check for sane values
334
  if (($consts_ref->{FIU_CFG_BITS} <= 0)) {
335
      $logger->error("FIU_CFG_BITS is <= 0.");
336
      return undef;
337
  }
338
339
  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}).");
340
      return undef;
341
  }
342
343
  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}).");
344
      return undef;
345
  }
346
347
  if (defined($consts_ref->{ID}) && ($consts_ref->{ID} <= 0 || $consts_ref->{ID} > (2**15 - 1))) {
      $logger->error("ID is invalid ($consts_ref->{ID}).");
348
      return undef;
349
  }
350
  if (($consts_ref->{BAUDRATE} <= 0)) {
351
      $logger->error("BAUDRATE missing is <= 0.");
352
      return undef;
353
  }
354
  return $consts_ref;
355
356
357
}

1;