#!/usr/bin/env python # Usage: python NLBConfig.py configfile freebiospath # Based on the commands in the user-supplied configfile, and several other # config files known to this program, this program generates a Makefile, # Makefile.settings, crt0.S file, etc. in the target directory, which is # specified by the 'target' command in the user's configfile. For more # info, see .../freebios/Documentation/Config # $Id$ # Author: # Modified by Jan Kok to improve readability of Makefile, etc. import sys import os import re import string debug = 0 # device variables superio_decls = '' superio_devices = [] numsuperio = 0 # Architecture variables arch = '' makebase = '' ldscriptbase = '' makeoptions = {} makeexpressions = [] # Key is the rule name. Value is a mkrule object. makebaserules = {} # List of targets in the order defined by makerule commands. makerule_targets = [] treetop = '' target_dir = '' sources = [] objectrules = [] userdefines = [] # ----------------------------------------------------------------------------- # Error Handling # ----------------------------------------------------------------------------- class location: class place: def __init__(self, file, line, command): self.file = file self.line = line self.command = command def next_line(self, command): self.line = self.line + 1 self.command = command def at(self): return "%s:%d" % (self.file, self.line) def __init__ (self): self.stack = [] def file(self): return self.stack[-1].file def line(self): return self.stack[-1].line def command(self): return self.stack[-1].command def push_file(self, file): self.stack.append(self.place(file, 0, "")) def pop_file(self): self.stack.pop() def next_line(self, command): self.stack[-1].next_line(command) def at(self): return self.stack[-1].at() loc = location() def fatal(string): global loc size = len(loc.stack) i = 0 while(i < size -1): print loc.stack[i].at() i = i + 1 print "%s: %s"% (loc.at(), string) sys.exit(1) def warning(string): global loc print "===> Warning:" size = len(loc.stack) i = 0 while(i < size -1): print loc.stack[i].at() i = i + 1 print "%s: %s"% (loc.at(), string) # ----------------------------------------------------------------------------- # Rules # ----------------------------------------------------------------------------- # this is the absolute base rule, and so is very special. mainrulelist = "all" def add_main_rule_dependency(new_dependency): global mainrulelist mainrulelist = mainrulelist + ' ' + new_dependency # function to add an object to the set of objects to be made. # this is a tuple, object name, source it depends on, # and an optional rule (can be empty) for actually building # the object def addobject(object, sourcepath, rule, condition, variable): sourcepath = topify(sourcepath) objectrules.append([object, sourcepath, rule, condition, variable]) # OK, let's face it, make sucks. # if you have a rule like this: # a.o: /some/long/path/a.c # make won't apply the .c.o rule. Toy! def addobject_defaultrule(object, sourcepath, condition, variable): # c_defaultrule = "$(CC) -c $(CFLAGS) -o $@ $<" c_defaultrule = "@echo $(CC) ... -o $@ $<\n\t@$(CC) -c $(CFLAGS) -o $@ $<" s_defaultrule = "@echo $(CC) ... -o $@ $<\n\t@$(CC) -c $(CPU_OPT) -o $@ $<" S_defaultrule = "@echo $(CPP) ... $< > $@\n\t@$(CPP) $(CPPFLAGS) $< >$@.new && mv $@.new $@" # Compute the source file name if (sourcepath[-2:] != '.c') and (sourcepath[-2:] != '.S'): base = object[:-2] sourcepath = os.path.join(sourcepath, base) + '.c' sources.append(sourcepath) if (sourcepath[-2:] == '.c'): defaultrule = "\t" + c_defaultrule elif (sourcepath[-2:] == '.S'): base = os.path.basename(sourcepath) s_name = base[:-2] + '.s' mkrule(s_name, sourcepath, [ S_defaultrule ] ) sourcepath = s_name defaultrule = "\t" + s_defaultrule else: fatal("Invalid object suffix") addobject(object, sourcepath, defaultrule, condition, variable) # ----------------------------------------------------------------------------- # Class for storing and printing make rules # ----------------------------------------------------------------------------- class mkrule: # This defines the function mkrule(target, depends, action). # It creates a makerule object and records it for later recall. def __init__(self, target, depends, actions): self.whence = loc.file() self.comments = [] # list of strings self.target = target # string self.depends = depends # string of dependency names self.actions = actions # list of strings if makebaserules.has_key(target): warning("makerule for target '%s' is replacing previous defintion in file %s" % (target, makebaserules[target].whence)) else: # Keep a list of targets so we can output the rules # in the order that they are defined. makerule_targets.append(target) # I remember myself, therefore I am. makebaserules[target] = self def adddepend(self, depend): self.depends = self.depends + ' ' + depend def addaction(self, action): self.actions.append(action) def write(self, file): file.write("\n") if 1: # Put comments before target : dependencies line. file.write("# from: %s\n" % self.whence) for comment in self.comments: file.write("# %s\n" % comment) # Write the target : dependencies line. file.write("%s: %s\n" % (self.target, self.depends)) if 0: # Put comments after target : dependencies line, # which causes them to be printed during make. file.write("\t# from: %s\n" % self.whence) for comment in self.comments: file.write("\t# %s\n" % comment) # Write out the actions. for action in self.actions: file.write("\t%s\n" % action) # ----------------------------------------------------------------------------- # Command parsing functions # ----------------------------------------------------------------------------- # Match a compiled pattern with a string, die if fails, return list of groups. def match(pattern, string): m = pattern.match(string) if not m: fatal("Bad command sytax") return m.groups() # A common pattern: splitargs_re = re.compile(r'(\S*)\s*(.*)') # ----------------------------------------------------------------------------- # Command execution functions # ----------------------------------------------------------------------------- # For all these functions, # dir is the directory that the current Config file is in # the second arg is the remainder of the command line (for most commands # any comments are stripped off) # COMMAND: target # target must be the first command in the file. This command may only be # used once. target names the target directory for building this # instance of the BIOS. The target directory will be $(TOP)/. def target(dir, targ_name): global target_dir target_dir = os.path.join(os.path.dirname(loc.file()), targ_name) if not os.path.isdir(target_dir): print 'Creating directory', target_dir os.makedirs(target_dir) print 'Will place Makefile, crt0.S, etc. in ', target_dir # dir should be the path to a directory containing a Config file. # If so, process the commands in that config file right now. def handleconfig(dir): file = os.path.join(dir, 'Config') print "Process config file: ", file if os.path.isfile(file): doconfigfile(dir, file) else: warning("%s not found" % file) # type is the command name, e.g. 'northbridge' # name is the path arg that followed the command # /src///Config is executed. def common_command_action(dir, type, name): realpath = os.path.join(treetop, 'src', type, name) handleconfig(realpath) return realpath # COMMAND: arch # is typically i386 or alpha. # Set various variables and execute a make.base file. def set_arch(dir, my_arch): global arch, makebase arch = my_arch configpath = os.path.join(treetop, "src/arch/", my_arch, "init") makebase = os.path.join(configpath, "make.base") print "Now Process the ", my_arch, " base files" if (debug): print "Makebase is :", makebase, ":" doconfigfile(treetop, makebase) # COMMAND: dir # Execute the config file at /Config # If begins with "/", it is interpreted as relative to $TOP, # otherwise it is interpreted as relative to the directory of the config # file that invoked this dir command. def dir(base_dir, name): regexp = re.compile(r"^/(.*)") m = regexp.match(name) if m and m.group(1): # /dir fullpath = os.path.join(treetop, m.group(1)) else: # dir fullpath = os.path.join(base_dir, name) handleconfig(fullpath) # The mainboard directory, as an absolute path. We need to remember this # because the docipl command generates Makefile code that includes files # from this directory. mainboard_dir = None # COMMAND: mainboard # The second command in a top-level config file is the mainboard command. # This command may only be used once. The mainboard command names a mainboard # source directory to use. The file $(TOP)/src//Config # is executed. def mainboard(dir, mainboard_name): global mainboard_dir set_option('MAINBOARD_PART_NUMBER', os.path.basename(mainboard_name)) set_option('MAINBOARD_VENDOR', os.path.dirname(mainboard_name)) mainboard_dir = common_command_action(dir, 'mainboard', mainboard_name) # COMMAND: etherboot # Execute the file $(TOP)/src/etherboot/Config and set some Makefile variables. def etherboot(dir, net_name): common_command_action(dir, 'etherboot', '') option(dir,'OLD_KERNEL_HACK') option(dir,'USE_TFTP') option(dir,'TFTP_INITRD') option(dir,'PYRO_SERIAL') # COMMAND: keyboard ... # old legacy PC junk, which will be dying soon. def keyboard(dir, keyboard_name): if (debug): print "KEYBOARD" keyboard_dir = os.path.join(treetop, 'src', keyboard_name) addobject_defaultrule('keyboard.o', keyboard_dir, '', 'OBJECTS') # COMMAND: cpu # # This command may only be used once for a given CPU name. Executes the # config file $(TOP)/src/cpu//Config # def cpu(dir, cpu_name): common_command_action(dir, 'cpu', cpu_name) # COMMAND: pmc / # # This command may only be used once. Executes the # config file $(TOP)/src/pmc///Config # def pmc(dir, pmc_name): common_command_action(dir, 'pmc', pmc_name) # COMMAND: northsouthbridge / # # This command may only be used once. Executes the config file # $(TOP)/src/northsouthbridge///Config # def northsouthbridge(dir, northsouthbridge_name): common_command_action(dir, 'northsouthbridge', northsouthbridge_name) # COMMAND: northbridge / # # This command may only be used once. Executes the config file # $(TOP)/src/northbridge///Config # def northbridge(dir, northbridge_name): common_command_action(dir, 'northbridge', northbridge_name) # COMMAND: southbridge / # # This command may only be used once. Executes the config file # $(TOP)/src/southbridge///Config # def southbridge(dir, southbridge_name): common_command_action(dir, 'southbridge', southbridge_name) # COMMAND: superio # # This command may be used as many times as needed. # I don't see a need yet to read in a config file for # superio. Don't bother. # def superio(dir, superio_name): # note that superio is w.r.t. treetop dir = os.path.join(treetop, 'src', 'superio', superio_name) addobject_defaultrule('superio.o', dir, '', 'OBJECTS') # COMMAND: nsuperio ... # commands are of the form: # superio_name [name=val]* def nsuperio(dir, superio_commands): global superio_decls, superio_devices, numsuperio, target_dir # note that superio is w.r.t. treetop (superio_name, rest) = match(splitargs_re, superio_commands) superio_decl_name = re.sub("/", "_", superio_name) dir = os.path.join(treetop, 'src', 'superio', superio_name) object="superio_%s.o" % superio_decl_name superio_source = dir + "/superio.c" addobject_defaultrule(object, superio_source, '', 'OBJECTS') addobject_defaultrule('nsuperio.o', "", '', 'OBJECTS') superio_cmds = ''; m = splitargs_re.match(rest) (cmd, rest) = m.groups() while (cmd): superio_cmds = superio_cmds + ", ." + cmd m = splitargs_re.match(rest) (cmd, rest) = m.groups() # now build the declaration decl = ''; decl = "extern struct superio_control superio_" decl = decl + superio_decl_name + "_control; \n" decl = decl + "struct superio superio_" + "%s_%d" %(superio_decl_name, numsuperio) decl = decl + "= { " decl = decl + "&superio_" + superio_decl_name+ "_control" decl = decl + superio_cmds + "};\n" superio_decls = superio_decls + decl; superio_devices.append("&superio_" + "%s_%d" % (superio_decl_name, numsuperio)); # note that we're using the new interface option(dir, "USE_NEW_SUPERIO_INTERFACE=1") numsuperio = numsuperio + 1 # A list of files for initializing a motherboard. mainboardinit_files = [] # COMMAND: mainboardinit [] # Include the assembly language .inc file at $(TOP)/src/ # in the crt0.s file. def mainboardinit(dir, command): (file, condition) = match(splitargs_re, command) mainboardinit_files.append( (file, condition) ) print "Added mainboard init file: ", file # COMMAND: raminit # Include the assembly language .inc file at $(TOP)/src/ # in the crt0.s file. This command is essentially a duplicate of mainboardinit. def raminit(dir, file): mainboardinit_files.append( (file, '') ) print "Added ram init file: ", file # Set of files we build the linker script from. ldscripts = [] # COMMAND: ldscript # $(TOP)/src/ will be added to the Makefile variable # LDSCRIPTS-1 if the is true or omitted. def ldscript(dir, command): (file, condition) = match(splitargs_re, command) filepath = os.path.join(treetop, 'src', file); ldscripts.append( (filepath, condition) ) print "Added ldscript init file: ", filepath # COMMAND: object [] # # is a pathname to a .c or .S file rooted at # $(TOP)/. # is a condition such as HAVE_PIRQ_TABLE. # This command adds a rule to the Makefile for # the target such that: # 1) Basename of .o depends on $(TOP)/ # 2) romimage has a dependency on the .o # 3) includepath for compiling the .o includes the directory containing the # source # An alternative explanation: The basename of path-to-source, minus the # .c or .S suffix, plus a .o suffix, is added to the OBJECTS-1 Makefile # variable if the is true or omitted. The OBJECTS-1 list is # used (by a makrule in $(TOP)/src/config/Config) to make linuxbios.a, # thus these objects are linked in only if needed to satisfy some reference. # def object(dir, command): (obj_name, condition) = match(splitargs_re, command) if (obj_name[-2:] == '.c') or (obj_name[-2:] == '.S'): source_name = os.path.join(dir, obj_name) obj_name = obj_name[:-2] + '.o' elif (obj_name[-2:] == '.o'): source_name = os.path.join(dir, obj_name[:-2] + '.c') else: fatal("Invalid object name") addobject_defaultrule(obj_name, source_name, condition, 'OBJECTS') # COMMAND: driver [] # # Similar to object command, but these objects go into the Makefile variable # DRIVERS-1 if the is true or omitted, and the objects listed # in DRIVERS-1 are always linked in (via $(TOP)/src/config/Config) to # linuxbios.o # def driver(dir, command): (obj_name, condition) = match(splitargs_re, command) addobject_defaultrule(obj_name, dir, condition, 'DRIVERS') # COMMAND: makerule : [] ; [] # # Lets you add make rules to the Makefile. # makerule_re = re.compile(r'([^:\s]+)\s*:\s*([^;]*?)\s*;\s*(.*)') def makerule(dir, rule): if (debug): print "makerule " , rule (target, dependencies, action) = match(makerule_re, rule) # Each rule created by makerule will be printed to the Makefile # with a comment which shows the config file from whence it came. mkrule(target, dependencies, [action]) # COMMAND: addaction # # Add an action to an existing rule designated by . # def addaction(dir, rule): if (debug): print "addaction " , rule (target, action) = match(splitargs_re, rule) if not makebaserules.has_key(target): warning("Need 'makerule %s ...' before addaction" % (target)) makebaserules[target].addaction(action) # COMMAND: adddepend # # Add a dependency to an existing rule designated by . # def adddepend(dir, rule): (target, depend) = match(splitargs_re, rule) if not makebaserules.has_key(target): warning("Need 'makerule %s ...' before adddepend" % (target)) makebaserules[target].adddepend(depend) # COMMAND: makedefine # is printed out to the Makefile. is usually of the form # = def makedefine(dir, rule): userdefines.append(rule) class mkexpr: class identifier: def __init__ (self, name): self.name = name def bc(self): return "($(" + self.name + "))" def perl(self): return "($(" + self.name + "))" class constant: def __init__ (self, value): self.value = value def bc(self): return "(" + self.value + ")" def perl(self): return "(" + self.value + ")" class unary: def __init__ (self, op, right): self.op = op self.right = right def bc(self): rstring = self.right.bc() if (self.op == "!"): result = "!" + rstring elif (self.op == "-"): result = "-" + rstring return "(" + result + ")" def perl(self): rstring = self.right.perl() if (self.op == "!"): result = "!" + rstring elif (self.op == "-"): result = "-" + rstring return "(" + result + ")" class binary: def __init__(self, op, left, right): self.op = op self.left = left self.right = right def bc(self): lstring = self.left.bc() rstring = self.right.bc() if (self.op == "&"): result = lstring + "&&" + rstring elif (self.op == "|"): result = lstring + "||" + rstring elif (self.op == "+"): result = lstring + "+" + rstring elif (self.op == "-"): result = lstring + "-" + rstring elif (self.op == "*"): result = lstring + "*" + rstring elif (self.op == "/"): result = lstring + "/" + rstring elif (self.op == "<<"): result = lstring + "*(2^" + rstring + ") " elif (self.op == ">>"): result = lstring + "/(2^" + rstring + ") " return "(" + result + ")" def perl(self): lstring = self.left.perl() rstring = self.right.perl() if (self.op == "&"): result = lstring + "&&" + rstring elif (self.op == "|"): result = lstring + "||" + rstring elif (self.op == "+"): result = lstring + "+" + rstring elif (self.op == "-"): result = lstring + "-" + rstring elif (self.op == "*"): result = lstring + "*" + rstring elif (self.op == "/"): result = lstring + "/" + rstring elif (self.op == "<<"): result = lstring + "<<" + rstring elif (self.op == ">>"): result = lstring + ">>" + rstring elif (self.op == ">"): result = lstring + ">" + rstring elif (self.op == ">="): result = lstring + ">=" + rstring elif (self.op == "<"): result = lstring + "<" + rstring elif (self.op == "<="): result = lstring + "<=" + rstring elif (self.op == "=="): result = lstring + "==" + rstring elif (self.op == "=="): result = lstring + "!=" + rstring return "(" + result + ")" class expression: def __init__(self, expr): self.expr = expr def bc(self): string = self.expr.bc() return "${shell echo '" + string + "' | bc}" def perl(self): string = self.expr.perl() return "${shell perl -e 'printf(\"%u\\n\", " + string + ");' }" # Tokens: ( ) ! | & + - * / << >> == <= >= < > != option ) start_re = re.compile(r"^\s*(([A-Za-z_][A-Za-z_0-9]*)|((0x[0-9A-Za-z]+)|([0-9]+))|(\()|(!))(.*)$") close_re = re.compile(r"^\s*\)(.*)$") # middle_re = re.compile(r"^\s*((\|)|(&)|(\+)|(-)|(\*)|(/)|(<<)|(>>)|(==)|(<=)|(>=)|(<)|(>))(.*)$") middle_re = re.compile(r"^\s*((\|)|(&)|(\+)|(-)|(\*)|(/)|(<<)|(>>)|>=|<=|>|<|==|!=)(.*)$") def __init__(self, expr): self.orig_expr = expr self.expr = "" def prec(self, op): if (op == "|"): result = 1 elif (op == "&"): result = 2 elif (op == "<") or (op == ">") or \ (op == ">=") or (op == "<=") or \ (op == "==") or (op == "!="): result = 3 elif (op == "+") or (op == "-"): result = 4 elif (op == "*") or (op == "/"): result = 5 elif (op == "<<") or (op == ">>"): result = 6 else: fatal("Unknown operator: %s" % op) def _parse_start(self): #print("start expr: %s"%(self.expr)) m = self.start_re.match(self.expr) if m: (token, self.expr) = (m.group(1), m.group(8)) if token == "(": left = self._parse() #print("close expr: %s"%(self.expr)) m = self.close_re.match(self.expr) if m: self.expr = m.group(1) else: fatal("No Matching )"); elif (token == "!"): right = self._parse() left = self.unary(token, right) elif m.group(2): if not makeoptions.has_key(token): fatal("Option %s not defined" % token) left = self.identifier(token) elif m.group(3): left = self.constant(token) else: fatal("Invalid expression: %s" % self.expr) return left def _parse_middle(self, left): #print("middle expr: %s"%(self.expr)) m = self.middle_re.match(self.expr) while(m): (op, self.expr) = (m.group(1),m.group(10)) right = self._parse_start() m = self.middle_re.match(self.expr) if m and (self.prec(op) < self.prec(m.group(1))): right = self._parse_middle() left = self.binary(op, left, right) return left def _parse(self): left = self._parse_start() return self._parse_middle(left) def parse(self): self.expr = self.orig_expr result = self._parse() if self.expr != "": fatal("Extra tokens: %s"% self.expr) return self.expression(result) # Put "