From 6f67cdd282f2351d7450e343314beeaa745f0159 Mon Sep 17 00:00:00 2001
From: Jozef Lawrynowicz <jozef.l@mittosystems.com>
Date: Tue, 6 Aug 2019 10:52:54 +0100
Subject: [PATCH 2/2] MSP430: Devices [2]: Read MCU data from external
 devices.csv file, if it exists

gcc/ChangeLog:

2019-08-XX  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* gcc/config/msp430/driver-msp430.c (msp430_set_driver_var): New.
	* gcc/config/msp430/msp430-devices.c (canonicalize_path_dirsep): New.
	(msp430_check_path_for_devices): New.
	(parse_devices_csv_1): New.
	(parse_devices_csv): New.
	(msp430_extract_mcu_data): Try to find devices.csv and search for the
	MCU data in devices.csv before using the hard-coded data.
	Warn if devices.csv isn't found and the MCU wasn't found in the
	hard-coded data either.
	* gcc/config/msp430/msp430.h (DRIVER_SELF_SPECS): Call
	msp430_set_driver_var for -mno-warn-devices-csv and -mdevices-csv-loc.
	Search for devices.csv on -I and -L paths.
	(EXTRA_SPEC_FUNCTIONS): Add msp430_check_path_for_devices and
	msp430_set_driver_var.
	* gcc/config/msp430/msp430.opt: Add -mwarn-devices-csv and
	-mdevices-csv-loc=.
	* gcc/doc/invoke.texi (-mmcu): Document that -I and -L paths are
	searched for devices.csv.
	(mwarn-devices-csv): Document option.

gcc/testsuite/ChangeLog:

2019-08-XX  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* gcc.target/msp430/msp430.exp (msp430_device_permutations_runtest):
	Handle csv-* and bad-devices-* tests.
	* gcc.target/msp430/devices/README: Document how bad-devices-* tests
	work.
	* gcc.target/msp430/devices/bad-devices-1.c: New test.
	* gcc.target/msp430/devices/bad-devices-2.c: Likewise.
	* gcc.target/msp430/devices/bad-devices-3.c: Likewise.
	* gcc.target/msp430/devices/bad-devices-4.c: Likewise.
	* gcc.target/msp430/devices/bad-devices-5.c: Likewise.
	* gcc.target/msp430/devices/bad-devices-6.c: Likewise.
	* gcc.target/msp430/devices/csv-device-order.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_00.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_01.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_02.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_04.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_08.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_10.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_11.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_12.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_14.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_18.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_20.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_21.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_22.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_24.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430_28.c: Likewise.
	* gcc.target/msp430/devices/csv-msp430fr5969.c: Likewise.
	* gcc.target/msp430/devices/hard-foo.c: Likewise.
	* gcc.target/msp430/devices/bad-devices-1.csv: New test support file.
	* gcc.target/msp430/devices/bad-devices-2.csv: Likewise.
	* gcc.target/msp430/devices/bad-devices-3.csv: Likewise.
	* gcc.target/msp430/devices/bad-devices-4.csv: Likewise.
	* gcc.target/msp430/devices/bad-devices-5.csv: Likewise.
	* gcc.target/msp430/devices/bad-devices-6.csv: Likewise.
	* gcc.target/msp430/devices/devices.csv: Likewise.
---
 gcc/config/msp430/driver-msp430.c             |  23 ++
 gcc/config/msp430/msp430-devices.c            | 276 +++++++++++++++++-
 gcc/config/msp430/msp430.h                    |  10 +-
 gcc/config/msp430/msp430.opt                  |   9 +
 gcc/doc/invoke.texi                           |  14 +-
 .../gcc.target/msp430/devices/README          |   5 +
 .../gcc.target/msp430/devices/bad-devices-1.c |   5 +
 .../msp430/devices/bad-devices-1.csv          |   3 +
 .../gcc.target/msp430/devices/bad-devices-2.c |   5 +
 .../msp430/devices/bad-devices-2.csv          |   3 +
 .../gcc.target/msp430/devices/bad-devices-3.c |   5 +
 .../msp430/devices/bad-devices-3.csv          |   3 +
 .../gcc.target/msp430/devices/bad-devices-4.c |   5 +
 .../msp430/devices/bad-devices-4.csv          |   3 +
 .../gcc.target/msp430/devices/bad-devices-5.c |   5 +
 .../msp430/devices/bad-devices-5.csv          |   3 +
 .../gcc.target/msp430/devices/bad-devices-6.c |   5 +
 .../msp430/devices/bad-devices-6.csv          |   3 +
 .../msp430/devices/csv-device-order.c         |  11 +
 .../gcc.target/msp430/devices/csv-msp430_00.c |   7 +
 .../gcc.target/msp430/devices/csv-msp430_01.c |   7 +
 .../gcc.target/msp430/devices/csv-msp430_02.c |   7 +
 .../gcc.target/msp430/devices/csv-msp430_04.c |   7 +
 .../gcc.target/msp430/devices/csv-msp430_08.c |   7 +
 .../gcc.target/msp430/devices/csv-msp430_10.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_11.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_12.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_14.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_18.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_20.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_21.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_22.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_24.c |   6 +
 .../gcc.target/msp430/devices/csv-msp430_28.c |   6 +
 .../msp430/devices/csv-msp430fr5969.c         |  11 +
 .../gcc.target/msp430/devices/devices.csv     |  22 ++
 .../gcc.target/msp430/devices/hard-foo.c      |   1 +
 gcc/testsuite/gcc.target/msp430/msp430.exp    |  16 +-
 38 files changed, 537 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.csv
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.csv
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.csv
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.csv
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.csv
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.csv
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-device-order.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_00.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_01.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_02.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_04.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_08.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_10.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_11.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_12.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_14.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_18.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_20.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_21.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_22.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_24.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430_28.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/csv-msp430fr5969.c
 create mode 100644 gcc/testsuite/gcc.target/msp430/devices/devices.csv

diff --git a/gcc/config/msp430/driver-msp430.c b/gcc/config/msp430/driver-msp430.c
index 9ad05dca957..4fa70c6c2e1 100644
--- a/gcc/config/msp430/driver-msp430.c
+++ b/gcc/config/msp430/driver-msp430.c
@@ -54,6 +54,29 @@ msp430_select_cpu (int argc, const char ** argv)
   return NULL;
 }
 
+/* Spec function to set a global variable to a specific value in the driver.
+   The first argument is the variable name, and the second is the value to set
+   it to.
+   Currently only "msp430_warn_devices_csv" and "msp430_devices_csv_loc" are
+   supported.
+   The intention is that we can take a "Target" option and set the variable
+   associated with it in the driver as well.  Whilst the driver sees "Target"
+   options, it does not set the variables associated with that option.  */
+const char *
+msp430_set_driver_var (int argc, const char ** argv)
+{
+  if (argc != 2)
+    error ("%<msp430_set_driver_var%> expects 2 arguments");
+  else if (strcmp (argv[0], "msp430_warn_devices_csv") == 0)
+    msp430_warn_devices_csv = atoi (argv[1]);
+  else if (strcmp (argv[0], "msp430_devices_csv_loc") == 0)
+    msp430_devices_csv_loc = argv[1];
+  else
+    error ("unhandled arguments %qs and %qs to %<msp430_set_driver_var%>",
+	   argv[0], argv[1]);
+  return NULL;
+}
+
 /* Implement spec function `msp430_hwmult_lib´.  */
 
 const char *
diff --git a/gcc/config/msp430/msp430-devices.c b/gcc/config/msp430/msp430-devices.c
index c5faff80487..0488a0f197c 100644
--- a/gcc/config/msp430/msp430-devices.c
+++ b/gcc/config/msp430/msp430-devices.c
@@ -35,8 +35,254 @@ struct t_msp430_mcu_data extracted_mcu_data;
 /* Initialized at the bottom of this file.  */
 extern struct t_msp430_mcu_data hard_msp430_mcu_data[605];
 
+/* Set to the full path to devices.csv if it is found by searching the -I and
+   -L paths.  */
+char * derived_devices_csv_loc = NULL;
+
+/* This is to canonicalize the directory separators in the path.
+   On Windows we could have a mix of '/' and '\' in the path.  */
+static void
+canonicalize_path_dirsep (char **path)
+{
+  char *t_path = *path;
+  int len = strlen (t_path);
+  int i;
+  for (i = 0; i < len; i++)
+    if (IS_DIR_SEPARATOR (t_path[i]))
+      t_path[i] = DIR_SEPARATOR;
+}
+
+/* Spec function which searches the paths passed to the -I and -L options for
+   the "devices.csv" file.  If it is found then the -mdevices-csv-loc option is
+   placed on the command line so the compiler knows the location of the
+   file.  */
+const char *
+msp430_check_path_for_devices (int argc, const char **argv)
+{
+  const char dirsep[2] = { DIR_SEPARATOR, 0 };
+  FILE * devices_file = NULL;
+  char * local_devices_csv_loc = NULL;
+  int i;
+  /* msp430_devices_csv_loc is set by -mdevices-csv-loc, derived_devices_csv_loc
+     is set by this function only.  */
+  if (msp430_devices_csv_loc || derived_devices_csv_loc)
+    return NULL;
+  for (i = 0; i < argc; i++)
+    {
+      char *inc_path = ASTRDUP (argv[i]);
+      canonicalize_path_dirsep (&inc_path);
+      if (!IS_DIR_SEPARATOR (inc_path[strlen (inc_path) - 1]))
+	inc_path = concat (inc_path, dirsep, NULL);
+      local_devices_csv_loc = concat (inc_path, "devices.csv", NULL);
+      devices_file = fopen (local_devices_csv_loc, "r");
+      if (devices_file != NULL)
+	{
+	  fclose (devices_file);
+	  derived_devices_csv_loc = local_devices_csv_loc;
+	  return concat ("-mdevices-csv-loc=", local_devices_csv_loc, NULL);
+	}
+    }
+  return NULL;
+}
+
+/* Search the devices.csv file for the given MCU name, and load the device
+   data into extracted_mcu_data.
+   Return 1 if MCU wasn't found in devices.csv, or the data couldn't be loaded
+   into extracted_mcu_data.
+   devices.csv has a specific format.  There is a row for column headings which
+   begins with "# Device Name".  The column numbers for CPU_TYPE (MSP430 ISA)
+   and MPY_TYPE (hwmult support) are extracted from this row and used later to
+   extract the ISA and hwmult supported for the given device.
+   The rows containing the MCU data are expected to begin immediately after the
+   column headings.  */
+static int
+parse_devices_csv_1 (const char * real_devices_csv_loc, const char * mcu_name)
+{
+  FILE * devices_file = fopen (real_devices_csv_loc, "r");
+  /* Some devices have a large number of errata, which means that MPY_TYPE
+     isn't found until the ~100th character in the line.  line_buf_size is set
+     to 200 to account for further possible additions to errata.  */
+  const size_t line_buf_size = 200;
+  char line[line_buf_size];
+  char * res;
+  bool found_headings = false;
+  bool found_mcu = false;
+  int cpu_type = -1;
+  int mpy_type = -1;
+  int cpu_type_column = -1;
+  int mpy_type_column = -1;
+  const char * device_name_heading = "# Device Name";
+  const char * cpu_type_heading = "CPU_TYPE";
+  const char * mpy_type_heading = "MPY_TYPE";
+  /* devices_file should never be NULL at this stage.  */
+  if (devices_file == NULL)
+    {
+      if (msp430_warn_devices_csv)
+	warning (0, "unexpected error opening %<devices.csv%>");
+      return 1;
+    }
+  while (1)
+    {
+      res = fgets (line, line_buf_size, devices_file);
+      if (res == NULL)
+	{
+	  /* The device has not been found in devices.csv.  Don't warn now in
+	     case it is in the hard-coded data.  We will warn later if the
+	     device was not found in the hard-coded data either.  */
+	  goto end;
+	}
+      else if (!found_headings
+	       && strncmp (line, device_name_heading,
+			   strlen (device_name_heading)) == 0)
+	{
+	  int curr_column = 0;
+	  char * heading = strtok (line, ",");
+	  found_headings = true;
+	  /* Find which column MPY_TYPE and CPU_TYPE are in.  */
+	  while (heading != NULL)
+	    {
+	      if (strncmp (heading, cpu_type_heading,
+			   strlen (cpu_type_heading)) == 0)
+		  cpu_type_column = curr_column;
+	      else if (strncmp (heading, mpy_type_heading,
+			   strlen (mpy_type_heading)) == 0)
+		  mpy_type_column = curr_column;
+	      if (cpu_type_column != -1 && mpy_type_column != -1)
+		break;
+	      heading = strtok (NULL, ",");
+	      curr_column++;
+	    }
+	  if (cpu_type_column == -1 || mpy_type_column == -1)
+	    {
+	      if (msp430_warn_devices_csv)
+		{
+		  if (cpu_type_column == -1 && mpy_type_column != -1)
+		    warning (0, "%<CPU_TYPE%> column heading is missing from "
+			     "%<devices.csv%>");
+		  else if (mpy_type_column == -1 && cpu_type_column != -1)
+		    warning (0, "%<MPY_TYPE%> column heading is missing from "
+			     "%<devices.csv%>");
+		  else
+		    warning (0, "%<CPU_TYPE%> and %<MPY_TYPE%> column headings "
+			     "are missing from %<devices.csv%>");
+		}
+	      goto end;
+	    }
+	}
+      else if (strncasecmp (line, mcu_name, strlen (mcu_name)) == 0
+	       && *(line + strlen (mcu_name)) == ',')
+	{
+	  if (!found_headings)
+	    {
+	      if (msp430_warn_devices_csv)
+		warning (0, "format of column headings in %<devices.csv%> "
+			 "is incorrect");
+	      goto end;
+	    }
+	  char * val = strtok (line, ",");
+	  int final_col_num = ((mpy_type_column > cpu_type_column)
+			       ? mpy_type_column : cpu_type_column);
+	  int curr_col;
+	  bool found_cpu = false;
+	  bool found_mpy = false;
+	  for (curr_col = 0; curr_col <= final_col_num; curr_col++)
+	    {
+	      /* Strip any new line characters from the last token.  */
+	      if (curr_col == final_col_num && strlen (val) > 1
+		  /* ASCII digit 10 == LF, 13 == CR.  */
+		  && (val[1] == 10 || val[1] == 13))
+		{
+		  /* Terminate the string after the first character.  */
+		  val[1] = 0;
+		}
+	      if (curr_col == cpu_type_column)
+		{
+		  cpu_type = atoi (val);
+		  /* Only a single '0', '1' or '2' is accepted.  */
+		  if (strlen (val) != 1
+		      /* atoi will return 0 if the string passed as an argument
+			 is empty or contains only whitespace characters, so we
+			 must error if 0 is returned but the first character in
+			 the original string is not '0'.  */
+		      || (cpu_type == 0 && val[0] != '0')
+		      || cpu_type > 2 || cpu_type < 0)
+		    {
+		      if (msp430_warn_devices_csv)
+			warning (0, "invalid %<CPU_TYPE%> value of %qs read "
+				 "from %<devices.csv%> for %qs", val, mcu_name);
+		      goto end;
+		    }
+		  extracted_mcu_data.revision = cpu_type;
+		  found_cpu = true;
+		}
+	      else if (curr_col == mpy_type_column)
+		{
+		  mpy_type = atoi (val);
+		  /* Only a single '0', '1', '2', '4' or '8' is accepted.  */
+		  if (strlen (val) != 1
+		      || (mpy_type == 0 && val[0] != '0')
+		      || !(mpy_type == 0
+			   || mpy_type == 1
+			   || mpy_type == 2
+			   || mpy_type == 4
+			   || mpy_type == 8))
+		    {
+		      if (msp430_warn_devices_csv)
+			warning (0, "invalid %<MPY_TYPE%> value of %qs read "
+				 "from %<devices.csv%> for %qs", val, mcu_name);
+		      goto end;
+		    }
+		  extracted_mcu_data.hwmpy = mpy_type;
+		  found_mpy = true;
+		}
+	      if (found_cpu && found_mpy)
+		{
+		  extracted_mcu_data.name = mcu_name;
+		  found_mcu = true;
+		  goto end;
+		}
+	      val = strtok (NULL, ",");
+	    }
+	  if (msp430_warn_devices_csv && (cpu_type == -1 || mpy_type == -1))
+	    warning (0, "unknown error reading %s from "
+		     "%<devices.csv%>",
+		     (cpu_type != -1 ? "%<MPY_TYPE%>"
+		      : (mpy_type != -1 ? "%<CPU_TYPE%>"
+			 : "%<CPU_TYPE%> and %<MPY_TYPE%>")));
+	  goto end;
+	}
+    }
+end:
+  fclose (devices_file);
+  if (!found_mcu)
+    return 1;
+  return 0;
+}
+
+/* Wrapper for the parse_devices_csv_1 work function.
+   A return code of 0 indicates that the MCU data has been successfully
+   extracted into extracted_mcu_data.
+   A return code of 1 indicates that the specified MCU wasn't found in
+   devices.csv.
+   A return code of 2 indicates that devices.csv wasn't found at all.  */
+static int
+parse_devices_csv (const char * mcu_name)
+{
+  /* First check if the path to devices.csv was set by -mdevices-csv-loc.  */
+  if (msp430_devices_csv_loc != NULL)
+    return parse_devices_csv_1 (msp430_devices_csv_loc, mcu_name);
+  /* Otherwise check if the path to devices.csv was found another way.  */
+  else if (derived_devices_csv_loc != NULL)
+    return parse_devices_csv_1 (derived_devices_csv_loc, mcu_name);
+  /* devices.csv was not found.  */
+  return 2;
+}
+
 /* Main entry point to load the MCU data for the given -mmcu into
-   extracted_mcu_data.  hard_msp430_mcu_data (initialized at the bottom of this
+   extracted_mcu_data.
+   First, the "devices.csv" MCU data file is searched for, if it is found, and
+   the MCU has a record in it, then that data is used.
+   Otherwise, hard_msp430_mcu_data (initialized at the bottom of this
    file) is searched for the MCU name.
    This function only needs to be executed once, but it can be first called
    from a number of different locations.  */
@@ -44,10 +290,27 @@ void
 msp430_extract_mcu_data (const char * mcu_name)
 {
   static int executed = 0;
+  int devices_csv_not_found = 0;
   int i;
   if (mcu_name == NULL || executed == 1)
     return;
   executed = 1;
+  /* If parse_devices_csv returns non-zero we need to use the
+     hard-coded data.  */
+  switch (parse_devices_csv (mcu_name))
+    {
+    case 0:
+      return;
+    case 1:
+      /* MCU not found in devices.csv.  Warn later if it's not in the
+	 hard-coded data either.  */
+      break;
+    case 2:
+      devices_csv_not_found = 1;
+      break;
+    default:
+      gcc_unreachable ();
+    }
   /* FIXME: This array is alpha sorted - we could use a binary search.  */
   for (i = ARRAY_SIZE (hard_msp430_mcu_data); i--;)
     if (strcasecmp (mcu_name, hard_msp430_mcu_data[i].name) == 0)
@@ -82,6 +345,17 @@ msp430_extract_mcu_data (const char * mcu_name)
 		       hard_msp430_mcu_data[i].revision);
 	}
     }
+  else if (msp430_warn_devices_csv && devices_csv_not_found)
+    warning (0, "could not locate MCU data file %<devices.csv%>");
+  else if (msp430_warn_mcu && extracted_mcu_data.name == NULL)
+    {
+      /* FIXME: We should warn here that the MCU name is unrecognized, but
+	 msp430_option_override will warn about an unrecognized MCU as well.
+	 The benefit of warning here is that this is code common to both the
+	 driver and compiler proper, so a warning will be emitted when
+	 assembling/linking via the driver, whilst msp430_option_override will
+	 only be called when preprocessing or compiling.  */
+    }
 }
 
 /* The data in this structure has been extracted from version 1.194 of the
diff --git a/gcc/config/msp430/msp430.h b/gcc/config/msp430/msp430.h
index b080dfbaca1..f23a460251f 100644
--- a/gcc/config/msp430/msp430.h
+++ b/gcc/config/msp430/msp430.h
@@ -75,14 +75,22 @@ extern bool msp430x;
     "%e-mcode-region requires the large memory model (-mlarge)}}"	\
   " %{!mlarge:%{mdata-region=*:"	\
     "%e-mdata-region requires the large memory model (-mlarge)}}"	\
+  " %{mno-warn-devices-csv:%:msp430_set_driver_var(msp430_warn_devices_csv 0)}"\
+  " %{mdevices-csv-loc=*:%:msp430_set_driver_var(msp430_devices_csv_loc %*)}"\
+  " %{I*:%:msp430_check_path_for_devices(%{I*:%*})}"       \
+  " %{L*:%:msp430_check_path_for_devices(%{L*:%*})}"       \
   " %{!mcpu=*:%{mmcu=*:%:msp430_select_cpu(%{mmcu=*:%*})}}"
 
 extern const char * msp430_select_hwmult_lib (int, const char **);
 extern const char * msp430_select_cpu (int, const char **);
+extern const char * msp430_set_driver_var (int, const char **);
+extern const char * msp430_check_path_for_devices (int, const char **);
 
 # define EXTRA_SPEC_FUNCTIONS				\
   { "msp430_hwmult_lib", msp430_select_hwmult_lib },	\
-  { "msp430_select_cpu", msp430_select_cpu },
+  { "msp430_select_cpu", msp430_select_cpu },		\
+  { "msp430_set_driver_var", msp430_set_driver_var },		\
+  { "msp430_check_path_for_devices", msp430_check_path_for_devices },
 
 /* Specify the libraries to include on the linker command line.
 
diff --git a/gcc/config/msp430/msp430.opt b/gcc/config/msp430/msp430.opt
index c027201bacd..cbbe0faa6a2 100644
--- a/gcc/config/msp430/msp430.opt
+++ b/gcc/config/msp430/msp430.opt
@@ -14,6 +14,10 @@ mwarn-mcu
 Target Report Var(msp430_warn_mcu) Init(1)
 Warn if an MCU name is unrecognized or conflicts with other options (default: on).
 
+mwarn-devices-csv
+Target Report Var(msp430_warn_devices_csv) Init(1)
+Warn if devices.csv is not found or there are problem parsing it (default: on).
+
 mcpu=
 Target Report Joined RejectNegative Var(target_cpu)
 Specify the ISA to build for: msp430, msp430x, msp430xv2.
@@ -92,3 +96,8 @@ Passes on a request to the assembler to enable fixes for various silicon errata.
 msilicon-errata-warn=
 Target Joined RejectNegative Report ToLower
 Passes on a request to the assembler to warn about various silicon errata.
+
+mdevices-csv-loc=
+Target Joined Var(msp430_devices_csv_loc) RejectNegative Report
+The path to devices.csv.  The GCC driver can normally locate devices.csv itself
+and pass this option to the compiler, so the user shouldn't need to pass this.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 01aab60f895..b3841aaeb11 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -23040,7 +23040,12 @@ command line.  The script's name is the name of the MCU with
 command line defines the C preprocessor symbol @code{__XXX__} and
 cause the linker to search for a script called @file{xxx.ld}.
 
-This option is also passed on to the assembler.
+The ISA and hardware multiply supported for the different MCUs is hard-coded
+into GCC.  However, an external @samp{devices.csv} file can be used to
+extend device support beyond those that have been hard-coded.
+
+GCC searches for the @samp{devices.csv} file on the paths specified
+with the @code{-I} and @code{-L} options.
 
 @item -mwarn-mcu
 @itemx -mno-warn-mcu
@@ -23129,6 +23134,13 @@ the named silicon errata.
 This option passes on a request to the assembler to enable warning
 messages when a silicon errata might need to be applied.
 
+@item -mwarn-devices-csv
+@itemx -mno-warn-devices-csv
+@opindex mwarn-devices-csv
+@opindex mno-warn-devices-csv
+Warn if @samp{devices.csv} is not found or there are problem parsing it
+(default: on).
+
 @end table
 
 @node NDS32 Options
diff --git a/gcc/testsuite/gcc.target/msp430/devices/README b/gcc/testsuite/gcc.target/msp430/devices/README
index 8d8c8f26db8..9134b4c4320 100644
--- a/gcc/testsuite/gcc.target/msp430/devices/README
+++ b/gcc/testsuite/gcc.target/msp430/devices/README
@@ -10,3 +10,8 @@ The criteria for this torture style of testing is:
 
 Some of the options used to run the tests will produce warnings/errors for the
 mcus, so ensure the test has dg-warning and dg-error directives as appropriate.
+
+The "bad-device-*.c" tests expect a corresponding "bad-devices-*.csv", which
+msp430.exp will pass as the argument to -mdevices-csv-loc=.  These tests are for
+checking the warnings in msp430-devices.c about a corrupted devices.csv work as
+expected.
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.c b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.c
new file mode 100644
index 00000000000..29ef85968d5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.c
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00 -mno-warn-mcu" } */
+/* { dg-warning "'CPU_TYPE' column heading is missing from 'devices.csv'" "" { target *-*-* } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.csv b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.csv
new file mode 100644
index 00000000000..282fa7bb292
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-1.csv
@@ -0,0 +1,3 @@
+# Test warning for missing CPU_TYPE heading
+# Device Name,FOO,MPY_TYPE
+msp430_00,0,0
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.c b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.c
new file mode 100644
index 00000000000..32e5cebd46e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.c
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00 -mno-warn-mcu" } */
+/* { dg-warning "'MPY_TYPE' column heading is missing from 'devices.csv'" "" { target *-*-* } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.csv b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.csv
new file mode 100644
index 00000000000..3bead68a85e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-2.csv
@@ -0,0 +1,3 @@
+# Test warning for missing MPY_TYPE heading
+# Device Name,CPU_TYPE,FOO
+msp430_00,0,0
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.c b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.c
new file mode 100644
index 00000000000..c982c4a7de5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.c
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00 -mno-warn-mcu" } */
+/* { dg-warning "'CPU_TYPE' and 'MPY_TYPE' column headings are missing from 'devices.csv'" "" { target *-*-* } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.csv b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.csv
new file mode 100644
index 00000000000..63b203005bd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-3.csv
@@ -0,0 +1,3 @@
+# Test warning for missing CPU_TYPE and MPY_TYPE headings
+# Device Name,FOO,BAR
+msp430_00,0,0
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.c b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.c
new file mode 100644
index 00000000000..89ebe61d179
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.c
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00 -mno-warn-mcu" } */
+/* { dg-warning "format of column headings in 'devices.csv' is incorrect" "" { target *-*-* } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.csv b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.csv
new file mode 100644
index 00000000000..4f25f93f288
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-4.csv
@@ -0,0 +1,3 @@
+# Test incorrectly formatted column headings
+# DeviceName,CPU_TYPE,MPY_TYPE
+msp430_00,0,0
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.c b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.c
new file mode 100644
index 00000000000..e33bac06b42
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.c
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00 -mno-warn-mcu" } */
+/* { dg-warning "invalid 'CPU_TYPE' value of '5' read from 'devices.csv' for 'msp430_00'" "" { target *-*-* } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.csv b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.csv
new file mode 100644
index 00000000000..32b70413a22
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-5.csv
@@ -0,0 +1,3 @@
+# Test bad CPU_TYPE value
+# Device Name,CPU_TYPE,MPY_TYPE
+msp430_00,5,0
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.c b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.c
new file mode 100644
index 00000000000..9dd9ed9e156
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.c
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00 -mno-warn-mcu" } */
+/* { dg-warning "invalid 'MPY_TYPE' value of '3' read from 'devices.csv' for 'msp430_00'" "" { target *-*-* } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.csv b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.csv
new file mode 100644
index 00000000000..0ec7152c475
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/bad-devices-6.csv
@@ -0,0 +1,3 @@
+# Test bad MPY_TYPE value
+# Device Name,CPU_TYPE,MPY_TYPE
+msp430_00,0,3
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-device-order.c b/gcc/testsuite/gcc.target/msp430/devices/csv-device-order.c
new file mode 100644
index 00000000000..172880321de
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-device-order.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-skip-if "MCU supports 430 ISA only" { *-*-* } { "-mlarge" "-mcpu=msp430x*" } { "" } } */
+/* { dg-additional-options "-mmcu=msp430f012 -mcpu=msp430 -mhwmult=16bit" } */
+
+/* Test that MCU names in devices.csv are only chosen if the full device name
+   is matched exactly.
+   msp430f0123 (with 430X ISA and f5series hwmult) appears before msp430f012 in
+   devices.csv, but should not be matched.
+   Errors and warnings will be emitted if msp430f0123 is wrongly matched.  */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_00.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_00.c
new file mode 100644
index 00000000000..8d56873140e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_00.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_00" } */
+/* { dg-warning "does not have hardware multiply" "" { target msp430_hwmul_not_none } 0 } */
+/* { dg-warning "supports 430 ISA but" "" { target msp430_430x_selected } 0 } */
+/* { dg-error "'-mlarge' requires a 430X-compatible '-mmcu='" "" { target msp430_mlarge_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_01.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_01.c
new file mode 100644
index 00000000000..154511b6fea
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_01.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_01" } */
+/* { dg-warning "supports 16-bit hardware multiply" "" { target msp430_hwmul_not_16bit } 0 } */
+/* { dg-warning "supports 430 ISA but" "" { target msp430_430x_selected } 0 } */
+/* { dg-error "'-mlarge' requires a 430X-compatible '-mmcu='" "" { target msp430_mlarge_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_02.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_02.c
new file mode 100644
index 00000000000..4c721638903
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_02.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_02" } */
+/* { dg-warning "supports 16-bit hardware multiply" "" { target msp430_hwmul_not_16bit } 0 } */
+/* { dg-warning "supports 430 ISA but" "" { target msp430_430x_selected } 0 } */
+/* { dg-error "'-mlarge' requires a 430X-compatible '-mmcu='" "" { target msp430_mlarge_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_04.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_04.c
new file mode 100644
index 00000000000..c8ed711eb1d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_04.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_04" } */
+/* { dg-warning "supports 32-bit hardware multiply" "" { target msp430_hwmul_not_32bit } 0 } */
+/* { dg-warning "supports 430 ISA but" "" { target msp430_430x_selected } 0 } */
+/* { dg-error "'-mlarge' requires a 430X-compatible '-mmcu='" "" { target msp430_mlarge_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_08.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_08.c
new file mode 100644
index 00000000000..0214e636644
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_08.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_08" } */
+/* { dg-warning "supports 32-bit .5xx. hardware multiply" "" { target msp430_hwmul_not_f5 } 0 } */
+/* { dg-warning "supports 430 ISA but" "" { target msp430_430x_selected } 0 } */
+/* { dg-error "'-mlarge' requires a 430X-compatible '-mmcu='" "" { target msp430_mlarge_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_10.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_10.c
new file mode 100644
index 00000000000..86f89ed51cf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_10.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_10" } */
+/* { dg-warning "does not have hardware multiply" "" { target msp430_hwmul_not_none } 0 } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_11.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_11.c
new file mode 100644
index 00000000000..2923238e6b8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_11.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_11" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 16-bit hardware multiply" "" { target msp430_hwmul_not_16bit } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_12.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_12.c
new file mode 100644
index 00000000000..ed59ac890c1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_12.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_12" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 16-bit hardware multiply" "" { target msp430_hwmul_not_16bit } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_14.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_14.c
new file mode 100644
index 00000000000..fb038a0d30f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_14.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_14" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 32-bit hardware multiply" "" { target msp430_hwmul_not_32bit } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_18.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_18.c
new file mode 100644
index 00000000000..133f984d031
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_18.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_18" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 32-bit .5xx. hardware multiply" "" { target msp430_hwmul_not_f5 } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_20.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_20.c
new file mode 100644
index 00000000000..c7c94252b00
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_20.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_20" } */
+/* { dg-warning "does not have hardware multiply" "" { target msp430_hwmul_not_none } 0 } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_21.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_21.c
new file mode 100644
index 00000000000..8794f891db7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_21.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_21" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 16-bit hardware multiply" "" { target msp430_hwmul_not_16bit } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_22.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_22.c
new file mode 100644
index 00000000000..47d90f4f730
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_22.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_22" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 16-bit hardware multiply" "" { target msp430_hwmul_not_16bit } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_24.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_24.c
new file mode 100644
index 00000000000..0a616307e86
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_24.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_24" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 32-bit hardware multiply" "" { target msp430_hwmul_not_32bit } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_28.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_28.c
new file mode 100644
index 00000000000..1243f7527e3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430_28.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430_28" } */
+/* { dg-warning "supports 430X ISA but '-mcpu' option is set to 430" "" { target msp430_430_selected } 0 } */
+/* { dg-warning "supports 32-bit .5xx. hardware multiply" "" { target msp430_hwmul_not_f5 } 0 } */
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/csv-msp430fr5969.c b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430fr5969.c
new file mode 100644
index 00000000000..636460756ad
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/csv-msp430fr5969.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-mmcu=msp430fr5969" } */
+/* MSP430FR5969 has msp430x ISA and f5series hwmult in the hard-coded data,
+   check that the different values for this device in devices.csv override it.
+   */
+/* { dg-warning "does not have hardware multiply" "" { target msp430_hwmul_not_none } 0 } */
+/* { dg-warning "supports 430 ISA but" "" { target msp430_430x_selected } 0 } */
+/* { dg-error "'-mlarge' requires a 430X-compatible '-mmcu='" "" { target msp430_mlarge_selected } 0 } */
+
+
+#include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/devices/devices.csv b/gcc/testsuite/gcc.target/msp430/devices/devices.csv
new file mode 100644
index 00000000000..7a13ed9dac4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/msp430/devices/devices.csv
@@ -0,0 +1,22 @@
+# Text before "#Device Name" is ignored.
+# We add arbitrary/empty fields in some records after the MPY_TYPE column to get
+# more varied testing.
+# Device Name,CPU_TYPE,MPY_TYPE
+msp430_00,0,0
+msp430_01,0,1,
+msp430_02,0,2,1
+msp430_04,0,4
+msp430_08,0,8,
+msp430_10,1,0,0,
+msp430_11,1,1
+msp430_12,1,2
+msp430_14,1,4,,
+msp430_18,1,8
+msp430_20,2,0
+msp430_21,2,1,4,
+msp430_22,2,2
+msp430_24,2,4
+msp430_28,2,8,100,
+msp430fr5969,0,0
+msp430f0123,2,8
+msp430f012,0,1
diff --git a/gcc/testsuite/gcc.target/msp430/devices/hard-foo.c b/gcc/testsuite/gcc.target/msp430/devices/hard-foo.c
index 802b0413452..e13acb52c50 100644
--- a/gcc/testsuite/gcc.target/msp430/devices/hard-foo.c
+++ b/gcc/testsuite/gcc.target/msp430/devices/hard-foo.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-mmcu=msp430foo" } */
+/* { dg-warning "could not locate MCU data file 'devices.csv'" "" { target *-*-* } 0 } */
 /* { dg-warning "Unrecognized MCU name 'msp430foo'.*\n.*Use the" "" { target *-*-* } 0 } */
 
 #include "../devices-main.c"
diff --git a/gcc/testsuite/gcc.target/msp430/msp430.exp b/gcc/testsuite/gcc.target/msp430/msp430.exp
index 20bfc149bd7..b97f4dcdc16 100644
--- a/gcc/testsuite/gcc.target/msp430/msp430.exp
+++ b/gcc/testsuite/gcc.target/msp430/msp430.exp
@@ -94,7 +94,21 @@ proc msp430_device_permutations_runtest { tests } {
 	    continue
 	}
 	foreach { mcu_flags } [msp430_get_opts $test_file] {
-	    dg-runtest $test_file "$mcu_flags" "$MSP430_DEFAULT_CFLAGS"
+	    if { [string match "csv-*" [file tail $test_file]] } {
+		# Specify the path to devices.csv for devices/csv-* tests with -I.
+		# Note that the csv-* tests do not have dg-options directives,
+		# they only have dg-additional-options to pass -mmcu.  This is
+		# so we can set the path to devices.csv as a "default" flag
+		# with -I, and the path won't show up in the test results
+		# summary.  If there were dg-options directives, then these
+		# default flags passed as the 3rd argument to dg-runtest would
+		# not be used.
+		dg-runtest $test_file "$mcu_flags" "-I[file dirname $test_file] $MSP430_DEFAULT_CFLAGS"
+	    } elseif { [string match "bad-devices*" [file tail $test_file]] } {
+		dg-runtest $test_file "$mcu_flags" "-mdevices-csv-loc=[file dirname $test_file]/[file tail $test_file]sv $MSP430_DEFAULT_CFLAGS"
+	    } else {
+		dg-runtest $test_file "$mcu_flags" "$MSP430_DEFAULT_CFLAGS"
+	    }
 	}
     }
 }
-- 
2.17.1

