]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
linuxdvb: starting to get config load/save for device tree
authorAdam Sutton <dev@adamsutton.me.uk>
Sat, 25 May 2013 20:46:37 +0000 (21:46 +0100)
committerAdam Sutton <dev@adamsutton.me.uk>
Sat, 25 May 2013 20:46:37 +0000 (21:46 +0100)
I think eventually I will re-work this to one file per entry similar
to the other trees (like network). But this will do for now.

src/input/mpegts.h
src/input/mpegts/linuxdvb/linuxdvb_adapter.c
src/input/mpegts/linuxdvb/linuxdvb_device.c
src/input/mpegts/linuxdvb/linuxdvb_frontend.c
src/input/mpegts/linuxdvb/linuxdvb_hardware.c
src/input/mpegts/linuxdvb/linuxdvb_mux.c
src/input/mpegts/linuxdvb/linuxdvb_private.h
src/prop.c

index 8cbfb1f991606a6ef5cc9b599c9557b3963a996f..a2ac78ecfc4a93548659c6b8ab515f5e637793ef 100644 (file)
@@ -252,28 +252,7 @@ struct mpegts_mux
    * Fields
    */
   char *mm_dvb_default_authority;
-  
-#if 0
-  dvb_mux_conf_t dm_conf;
-
-  char *dm_default_authority;
-
-  TAILQ_HEAD(, epggrab_ota_mux) dm_epg_grab;
-#endif
-
-#if 0 // TODO: do we need this here? or linuxdvb?
-  struct th_dvb_mux_instance *dm_current_tdmi;
-
-  struct th_dvb_mux_instance_list dm_tdmis;
-#endif
-
-#if 0 // TODO: what about these?
-  // Derived from dm_conf (more or less)
-  char *dm_local_identifier;
-
-  int dm_enabled; // TODO: could be derived?
-#endif
-
+  int   mm_enabled;
 };
  
 /* Service */
index f560cbaf632cb6a5706b49ef79534e0cd3a3f75b..05a26c660c3787b546cee5845a024e3665f805ce 100644 (file)
  * DVB Adapter
  * **************************************************************************/
 
+static void
+linuxdvb_adapter_class_save ( idnode_t *in )
+{
+  linuxdvb_adapter_t *la = (linuxdvb_adapter_t*)in;
+  linuxdvb_device_save((linuxdvb_device_t*)la->lh_parent);
+}
+
 const idclass_t linuxdvb_adapter_class =
 {
   .ic_super      = &linuxdvb_hardware_class,
   .ic_class      = "linuxdvb_adapter",
   .ic_caption    = "LinuxDVB Adapter",
+  .ic_save       = linuxdvb_adapter_class_save,
   .ic_properties = (const property_t[]){
     { PROPDEF2("rootpath", "Device Path",
                PT_STR, linuxdvb_adapter_t, la_rootpath, 1) },
@@ -45,6 +53,30 @@ const idclass_t linuxdvb_adapter_class =
   }
 };
 
+/*
+ * Save data
+ */
+void
+linuxdvb_adapter_save ( linuxdvb_adapter_t *la, htsmsg_t *m )
+{
+  htsmsg_t *l;
+  linuxdvb_hardware_t *lh;
+
+  linuxdvb_hardware_save((linuxdvb_hardware_t*)la, m);
+  htsmsg_add_u32(m, "number", la->la_number);
+  if (la->la_rootpath)
+    htsmsg_add_str(m, "rootpath", la->la_rootpath);
+
+  /* Frontends */
+  l = htsmsg_create_map();
+  LIST_FOREACH(lh, &la->lh_children, lh_parent_link) {
+    htsmsg_t *e = htsmsg_create_map();
+    linuxdvb_frontend_save((linuxdvb_frontend_t*)lh, e);
+    htsmsg_add_msg(l, idnode_uuid_as_str(&lh->mi_id), e);
+  }
+  htsmsg_add_msg(m, "frontends", l);
+}
+
 /*
  * Check is free
  */
@@ -66,9 +98,14 @@ linuxdvb_adapter_current_weight ( linuxdvb_adapter_t *la )
 /*
  * Create
  */
-static linuxdvb_adapter_t *
-linuxdvb_adapter_create0 ( const char *uuid, linuxdvb_device_t *ld, int n )
+linuxdvb_adapter_t *
+linuxdvb_adapter_create0 
+  ( linuxdvb_device_t *ld, const char *uuid, htsmsg_t *conf )
 {
+  uint32_t u32;
+  const char *str;
+  htsmsg_t *e;
+  htsmsg_field_t *f;
   linuxdvb_adapter_t *la;
 
   la = calloc(1, sizeof(linuxdvb_adapter_t));
@@ -76,11 +113,28 @@ linuxdvb_adapter_create0 ( const char *uuid, linuxdvb_device_t *ld, int n )
     free(la);
     return NULL;
   }
-  la->la_number = n;
 
   LIST_INSERT_HEAD(&ld->lh_children, (linuxdvb_hardware_t*)la, lh_parent_link);
   la->lh_parent = (linuxdvb_hardware_t*)ld;
 
+  /* No conf */
+  if (!conf)
+    return la;
+
+  linuxdvb_hardware_load((linuxdvb_hardware_t*)la, conf);
+  if (!htsmsg_get_u32(conf, "number", &u32))
+    la->la_number = u32;
+  if ((str = htsmsg_get_str(conf, "rootpath")))
+    la->la_rootpath = strdup(str);
+
+  /* Frontends */
+  if ((conf = htsmsg_get_map(conf, "frontends"))) {
+    HTSMSG_FOREACH(f, conf) {
+      if (!(e = htsmsg_get_map_by_field(f))) continue;
+      (void)linuxdvb_frontend_create0(la, f->hmf_name, e, 0);
+    }
+  }
+
   return la;
 }
 
@@ -110,11 +164,12 @@ linuxdvb_adapter_find_by_number ( int adapter )
 
   /* Create */
   if (!la) {
-    if (!(la = linuxdvb_adapter_create0(NULL, ld, a)))
+    if (!(la = linuxdvb_adapter_create0(ld, NULL, NULL)))
       return NULL;
   }
 
   /* Update */
+  la->la_number = a;
   snprintf(buf, sizeof(buf), "/dev/dvb/adapter%d", adapter);
   tvh_str_update(&la->la_rootpath, buf);
   if (!la->lh_displayname)
@@ -181,7 +236,8 @@ linuxdvb_adapter_added ( int adapter )
     linuxdvb_frontend_added(la, i, fe_path, dmx_path, dvr_path, &dfi);
   }
 
+  if (la)
+    linuxdvb_device_save((linuxdvb_device_t*)la->lh_parent);
+
   return la;
 }
-
-
index 3025f12882b56465538b2ef6e714a4590608e419..c268d3eabb027e14e81c48aa52f6f602d023d87a 100644 (file)
@@ -48,6 +48,11 @@ devinfo_bus2str ( int p )
 {
   return val2str(p, bustab);
 }
+static int
+devinfo_str2bus ( const char *str )
+{
+  return str2val(str, bustab);
+}
 
 /*
  * Get bus information
@@ -143,12 +148,70 @@ get_device_info ( device_info_t *di, int a )
   }
   di->di_id = strdup(buf);
 }
+static void
+get_min_dvb_adapter ( device_info_t *di )
+{
+  int mina = -1;
+  char path[512];
+  DIR *dp;
+  struct dirent *de;
+
+  snprintf(path, sizeof(path), "/sys/bus/%s/devices/%s/dvb",
+           di->di_bus == BUS_PCI ? "pci" : "usb", di->di_path);
+
+  /* Find minimum adapter number */
+  if ((dp = opendir(path))) {
+    while ((de = readdir(dp))) {
+      int t;
+      if ((sscanf(de->d_name, "dvb%d.frontend0", &t)))
+        if (mina == -1 || t < mina) mina = t;
+    }
+  }
+  di->di_min_adapter = mina;
+}
+
+
+static void
+linuxdvb_device_class_save ( idnode_t *in )
+{
+  linuxdvb_device_save((linuxdvb_device_t*)in);
+}
+void linuxdvb_device_save ( linuxdvb_device_t *ld )
+{
+  htsmsg_t *m, *e, *l;
+  linuxdvb_hardware_t *lh;
+
+  m = htsmsg_create_map();
+
+  linuxdvb_hardware_save((linuxdvb_hardware_t*)ld, m);
+  if (ld->ld_devid.di_id) {
+    htsmsg_add_str(m, "devid",   ld->ld_devid.di_id);
+    htsmsg_add_str(m, "devbus",  devinfo_bus2str(ld->ld_devid.di_bus));
+    htsmsg_add_str(m, "devpath", ld->ld_devid.di_path);
+  } else {
+    printf("OH NO, no device ID\n");
+  }
+  
+  /* Adapters */
+  l = htsmsg_create_map();
+  LIST_FOREACH(lh, &ld->lh_children, lh_parent_link) {
+    e = htsmsg_create_map();
+    linuxdvb_adapter_save((linuxdvb_adapter_t*)lh, e);
+    htsmsg_add_msg(l, idnode_uuid_as_str(&lh->mi_id), e);
+  }
+  htsmsg_add_msg(m, "adapters", l);
+
+  /* Save */
+  hts_settings_save(m, "input/linuxdvb/devices/%s",
+                    idnode_uuid_as_str(&ld->mi_id));
+}
 
 const idclass_t linuxdvb_device_class =
 {
   .ic_super      = &linuxdvb_hardware_class,
   .ic_class      = "linuxdvb_device",
   .ic_caption    = "LinuxDVB Device",
+  .ic_save       = linuxdvb_device_class_save,
   .ic_properties = (const property_t[]){
     { PROPDEF2("devid", "Device ID",
                PT_STR, linuxdvb_device_t, ld_devid.di_id, 1) },
@@ -170,6 +233,8 @@ linuxdvb_device_create0 ( const char *uuid, htsmsg_t *conf )
   uint32_t u32;
   const char *str;
   linuxdvb_device_t *ld;
+  htsmsg_t *e;
+  htsmsg_field_t *f;
 
   /* Create */
   ld = calloc(1, sizeof(linuxdvb_device_t));
@@ -184,15 +249,26 @@ linuxdvb_device_create0 ( const char *uuid, htsmsg_t *conf )
     return ld;
 
   /* Load config */
+  linuxdvb_hardware_load((linuxdvb_hardware_t*)ld, conf);
   if (!htsmsg_get_u32(conf, "enabled", &u32) && u32)
     ld->lh_enabled     = 1;
   if ((str = htsmsg_get_str(conf, "displayname")))
     ld->lh_displayname = strdup(str);
   if ((str = htsmsg_get_str(conf, "devid")))
-    strncpy(ld->ld_devid.di_id, str, sizeof(ld->ld_devid.di_id));
+    ld->ld_devid.di_id = strdup(str);
+  if ((str = htsmsg_get_str(conf, "devbus")))
+    ld->ld_devid.di_bus = devinfo_str2bus(str);
+  if ((str = htsmsg_get_str(conf, "devpath")))
+    strncpy(ld->ld_devid.di_path, str, sizeof(ld->ld_devid.di_path));
+  get_min_dvb_adapter(&ld->ld_devid);
 
-  // TODO: adapters
-  // TODO: frontends
+  /* Adapters */
+  if ((conf = htsmsg_get_map(conf, "adapters"))) {
+    HTSMSG_FOREACH(f, conf) {
+      if (!(e = htsmsg_get_map_by_field(f))) continue;
+      (void)linuxdvb_adapter_create0(ld, f->hmf_name, e);
+    }
+  }
 
   return ld;
 }
@@ -247,7 +323,7 @@ void linuxdvb_device_init ( int adapter_mask )
       }
     }
   }
-  
+
   /* Scan for hardware */
   if ((dp = opendir("/dev/dvb"))) {
     struct dirent *de;
index 1dba30e3fc66c2152123a694f444ecc0c8da7c9b..45f790b2d8957714b7796cd0bbd27a6611b2c2cd 100644 (file)
@@ -39,12 +39,20 @@ linuxdvb_frontend_class_get_title ( idnode_t *in )
   return "unknown";
 }
 
+static void
+linuxdvb_frontend_class_save ( idnode_t *in )
+{
+  linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)in;
+  linuxdvb_device_save((linuxdvb_device_t*)lfe->lh_parent->lh_parent);
+}
+
 const idclass_t linuxdvb_frontend_class =
 {
   .ic_super      = &linuxdvb_hardware_class,
   .ic_class      = "linuxdvb_frontend",
   .ic_caption    = "Linux DVB Frontend",
   .ic_get_title  = linuxdvb_frontend_class_get_title,
+  .ic_save       = linuxdvb_frontend_class_save,
   .ic_properties = (const property_t[]) {
     { PROPDEF2("fe_path", "Frontend Path",
                PT_STR, linuxdvb_frontend_t, lfe_fe_path, 1) },
@@ -100,6 +108,19 @@ const idclass_t linuxdvb_frontend_atsc_class =
  * Frontend
  * *************************************************************************/
 
+void
+linuxdvb_frontend_save ( linuxdvb_frontend_t *lfe, htsmsg_t *m )
+{
+  htsmsg_add_u32(m, "number", lfe->lfe_number);
+  htsmsg_add_str(m, "type", dvb_type2str(lfe->lfe_info.type));
+  if (lfe->lfe_fe_path)
+    htsmsg_add_str(m, "fe_path", lfe->lfe_fe_path);
+  if (lfe->lfe_dmx_path)
+    htsmsg_add_str(m, "dmx_path", lfe->lfe_dmx_path);
+  if (lfe->lfe_dvr_path)
+    htsmsg_add_str(m, "dvr_path", lfe->lfe_dvr_path);
+}
+
 static int
 linuxdvb_frontend_is_free ( mpegts_input_t *mi )
 {
@@ -250,12 +271,21 @@ linuxdvb_frontend_close_service
 {
 }
 
-static linuxdvb_frontend_t *
+linuxdvb_frontend_t *
 linuxdvb_frontend_create0
-  ( const char *uuid, linuxdvb_adapter_t *la, int num, fe_type_t type )
+  ( linuxdvb_adapter_t *la, const char *uuid, htsmsg_t *conf, fe_type_t type )
 {
+  uint32_t u32;
+  const char *str;
   const idclass_t *idc;
 
+  /* Get type */
+  if (conf) {
+    if (!(str = htsmsg_get_str(conf, "type")))
+      return NULL;
+    type = dvb_str2type(str);
+  }
+
   /* Class */
   if (type == FE_QPSK)
     idc = &linuxdvb_frontend_dvbs_class;
@@ -273,6 +303,7 @@ linuxdvb_frontend_create0
   linuxdvb_frontend_t *lfe
     = (linuxdvb_frontend_t*)
         mpegts_input_create0(calloc(1, sizeof(linuxdvb_frontend_t)), idc, uuid);
+  lfe->lfe_info.type = type;
 
   /* Input callbacks */
   lfe->mi_start_mux      = linuxdvb_frontend_start_mux;
@@ -286,6 +317,14 @@ linuxdvb_frontend_create0
   lfe->lh_parent = (linuxdvb_hardware_t*)la;
   LIST_INSERT_HEAD(&la->lh_children, (linuxdvb_hardware_t*)lfe, lh_parent_link);
 
+  /* No conf */
+  if (!conf)
+    return lfe;
+
+  if (!htsmsg_get_u32(conf, "number", &u32))
+    lfe->lfe_number = u32; 
+  // TODO: network
+
   return lfe;
 }
 
@@ -315,13 +354,14 @@ linuxdvb_frontend_added
 
   /* Create new */
   if (!lfe) {
-    if (!(lfe = linuxdvb_frontend_create0(NULL, la, fe_num, fe_info->type))) {
+    if (!(lfe = linuxdvb_frontend_create0(la, NULL, NULL, fe_info->type))) {
       tvhlog(LOG_ERR, "linuxdvb", "failed to create frontend");
       return NULL;
     }
   }
 
   /* Copy info */
+  lfe->lfe_number = fe_num;
   memcpy(&lfe->lfe_info, fe_info, sizeof(struct dvb_frontend_info));
 
   /* Set paths */
index 1e7b92141f02bd8de3d0119e08f0c0e22bc6cf5b..0c62c5c1e74fbdccf393727160170074bdc5850a 100644 (file)
@@ -45,6 +45,23 @@ linuxdvb_hardware_enumerate ( linuxdvb_hardware_list_t *list )
   return v;
 }
 
+void linuxdvb_hardware_save ( linuxdvb_hardware_t *lh, htsmsg_t *m )
+{
+  htsmsg_add_u32(m, "enabled", lh->lh_enabled);
+  if (lh->lh_displayname)
+    htsmsg_add_str(m, "displayname", lh->lh_displayname);
+}
+
+void linuxdvb_hardware_load ( linuxdvb_hardware_t *lh, htsmsg_t *conf )
+{
+  uint32_t u32;
+  const char *str;
+  if (!htsmsg_get_u32(conf, "enabled", &u32) && u32)
+    lh->lh_enabled     = 1;
+  if ((str = htsmsg_get_str(conf, "displayname")))
+    lh->lh_displayname = strdup(str);
+}
+
 static const char *
 linuxdvb_hardware_class_get_title ( idnode_t *in )
 {
index 90248353c8072cd0cdb096ace4d743d0c1eab69e..a7f446d055f0b2838a53963c68d7bc471a5749eb 100644 (file)
@@ -116,13 +116,64 @@ linuxdvb_mux_close_table ( mpegts_mux_t *mm, mpegts_table_t *mt )
 {
 }
 
+static const char *
+dvb_mux_conf_load ( fe_type_t type, dvb_mux_conf_t *dmc, htsmsg_t *m )
+{
+  //const char *s;
+  dmc->dmc_fe_params.inversion = INVERSION_AUTO;
+  htsmsg_get_u32(m, "frequency", &dmc->dmc_fe_params.frequency);
+
+#if 0
+  switch(tda->tda_type) {
+  case FE_OFDM:
+    s = htsmsg_get_str(m, "bandwidth");
+    if(s == NULL || (r = str2val(s, bwtab)) < 0)
+      return "Invalid bandwidth";
+    dmc->dmc_fe_params.u.ofdm.bandwidth = r;
+
+    s = htsmsg_get_str(m, "constellation");
+    if(s == NULL || (r = str2val(s, qamtab)) < 0)
+      return "Invalid QAM constellation";
+    dmc->dmc_fe_params.u.ofdm.constellation = r;
+
+    s = htsmsg_get_str(m, "transmission_mode");
+    if(s == NULL || (r = str2val(s, modetab)) < 0)
+      return "Invalid transmission mode";
+    dmc->dmc_fe_params.u.ofdm.transmission_mode = r;
+
+    s = htsmsg_get_str(m, "guard_interval");
+    if(s == NULL || (r = str2val(s, guardtab)) < 0)
+      return "Invalid guard interval";
+    dmc->dmc_fe_params.u.ofdm.guard_interval = r;
+
+    s = htsmsg_get_str(m, "hierarchy");
+    if(s == NULL || (r = str2val(s, hiertab)) < 0)
+      return "Invalid heirarchy information";
+    dmc->dmc_fe_params.u.ofdm.hierarchy_information = r;
+
+    s = htsmsg_get_str(m, "fec_hi");
+    if(s == NULL || (r = str2val(s, fectab)) < 0)
+      return "Invalid hi-FEC";
+    dmc->dmc_fe_params.u.ofdm.code_rate_HP = r;
+
+    s = htsmsg_get_str(m, "fec_lo");
+    if(s == NULL || (r = str2val(s, fectab)) < 0)
+      return "Invalid lo-FEC";
+    dmc->dmc_fe_params.u.ofdm.code_rate_LP = r;
+    break;
+  }
+#endif
+  return "Not yet supported";
+}
 
 linuxdvb_mux_t *
 linuxdvb_mux_create0
   ( linuxdvb_network_t *ln, const char *uuid, htsmsg_t *conf )
 {
-  //uint32_t u32;
-  //const char *str;
+  uint32_t u32;
+  const char *str;
+  htsmsg_t *c, *e;
+  htsmsg_field_t *f;
   mpegts_mux_t *mm;
   linuxdvb_mux_t *lm;
   const idclass_t *idc;
@@ -151,10 +202,6 @@ linuxdvb_mux_create0
   
   /* Callbacks */
   lm->mm_config_save = linuxdvb_mux_config_save;
-#if 0
-  lm->mm_start       = linuxdvb_mux_start;
-  lm->mm_stop        = linuxdvb_mux_stop;
-#endif
   lm->mm_open_table  = linuxdvb_mux_open_table;
   lm->mm_close_table = linuxdvb_mux_close_table;
 
@@ -163,6 +210,30 @@ linuxdvb_mux_create0
     return lm;
 
   /* Config */
+  // TODO: this could go in mpegts_mux
+  if (!htsmsg_get_u32(conf, "enabled", &u32) && u32)
+    lm->mm_enabled = 1;
+  if (!htsmsg_get_u32(conf, "onid", &u32))
+    lm->mm_onid = u32;
+  if (!htsmsg_get_u32(conf, "tsid", &u32))
+    lm->mm_tsid = u32;
+  if ((str = htsmsg_get_str(conf, "default_authority")))
+    lm->mm_dvb_default_authority = strdup(str);
+
+  /* Tuning info */
+  if ((e = htsmsg_get_map(conf, "tuning")))
+    (void)dvb_mux_conf_load(ln->ln_type, &lm->lm_tuning, e);
+
+  /* Services */
+  if ((c = hts_settings_load_r(1, "input/linuxdvb/networks/%s/muxes/%s/services",
+                               "TODO", uuid))) {
+    HTSMSG_FOREACH(f, c) {
+      if (!(e = htsmsg_get_map_by_field(f))) continue;
+      if (!(e = htsmsg_get_map(e, "config"))) continue;
+      //(void)linuxdvb_service_create0(lm, f->hmf_name, e);
+    }
+    htsmsg_destroy(c);
+  }
 
   return lm;
 }
index e9a7595feffc04fc3e219bbaa081721057b72d08..985602e38853dfff75ff622758febe593b663ee7 100644 (file)
@@ -70,6 +70,8 @@ extern const idclass_t linuxdvb_hardware_class;
 
 idnode_t **
 linuxdvb_hardware_enumerate ( linuxdvb_hardware_list_t *list );
+void linuxdvb_hardware_save ( linuxdvb_hardware_t *lh, htsmsg_t *m );
+void linuxdvb_hardware_load ( linuxdvb_hardware_t *lh, htsmsg_t *m );
 
 
 struct linuxdvb_device
@@ -83,6 +85,7 @@ struct linuxdvb_device
 };
 
 void linuxdvb_device_init ( int adapter_mask );
+void linuxdvb_device_save ( linuxdvb_device_t *ld );
 
 linuxdvb_device_t *linuxdvb_device_create0
   (const char *uuid, htsmsg_t *conf);
@@ -103,6 +106,11 @@ struct linuxdvb_adapter
 #define LINUXDVB_SUBSYS_FE  0x01
 #define LINUXDVB_SUBSYS_DVR 0x02
 
+void linuxdvb_adapter_save ( linuxdvb_adapter_t *la, htsmsg_t *m );
+
+linuxdvb_adapter_t *linuxdvb_adapter_create0
+  ( linuxdvb_device_t *ld, const char *uuid, htsmsg_t *conf );
+
 linuxdvb_adapter_t *linuxdvb_adapter_added (int a);
 
 int  linuxdvb_adapter_is_free        ( linuxdvb_adapter_t *la );
@@ -131,11 +139,19 @@ struct linuxdvb_frontend
   gtimer_t                  lfe_monitor_timer;
 };
 
+linuxdvb_frontend_t *
+linuxdvb_frontend_create0 
+  ( linuxdvb_adapter_t *la, const char *uuid, htsmsg_t *conf, fe_type_t type ); 
+
+void linuxdvb_frontend_save ( linuxdvb_frontend_t *lfe, htsmsg_t *m );
+
 linuxdvb_frontend_t *
 linuxdvb_frontend_added
   ( linuxdvb_adapter_t *la, int fe_num,
     const char *fe_path, const char *dmx_path, const char *dvr_path,
     const struct dvb_frontend_info *fe_info );
+void linuxdvb_frontend_add_network
+  ( linuxdvb_frontend_t *lfe, linuxdvb_network_t *net );
 
 struct linuxdvb_network
 {
@@ -157,7 +173,7 @@ struct linuxdvb_mux
   /*
    * Tuning information
    */
-  dvb_mux_conf_t lm_tune_conf;
+  dvb_mux_conf_t lm_tuning;
 };
 
 linuxdvb_mux_t *linuxdvb_mux_create0
index b025e6c9f2c74856a09542104b6f0d6e4056502e..899662b5c48923574c75292d05dc5fc268c963a8 100644 (file)
@@ -169,6 +169,8 @@ prop_seti(void *obj, const property_t *p, const char *value)
   int i32;
   const char *s;
 
+  if (p->rdonly) return 0;
+
   void *val = obj + p->off;
   switch(p->type) {