5
* @brief this file is the main source file for the implementation of dynamic registers using xml and several funtionalities for the "pci-tool" line command tool from XML files. the xml part has been implemented using libxml2
7
* @details registers and banks nodes are parsed using xpath expression, but their properties is get by recursively getting all properties nodes. In this sense, if a property changes, the algorithm has to be changed, but if it's registers or banks nodes, just the xpath expression modification should be enough.
9
Libxml2 considers blank spaces within the XML as node natively and this code as been produced considering blank spaces in the XML files. In case XML files would not contain blank spaces anymore, please change the code.
11
In case the performance is not good enough, please consider the following : hard code of formulas
13
#define _XOPEN_SOURCE 700
15
#define _DEFAULT_SOURCE
25
#include <sys/types.h>
29
#include <libxml/xmlschemastypes.h>
30
#include <libxml/tree.h>
31
#include <libxml/parser.h>
32
#include <libxml/xpath.h>
33
#include <libxml/xpathInternals.h>
43
#define BANKS_PATH ((xmlChar*)"/model/banks/bank/bank_description") /**< path to complete nodes of banks.*/
44
//#define REGISTERS_PATH ((xmlChar*)"/model/banks/bank/registers/register") /**< all standard registers nodes.*/
45
//#define BIT_REGISTERS_PATH ((xmlChar*)"/model/banks/bank/registers/register/registers_bits/register_bits") /**< all bits registers nodes.*/
46
#define REGISTERS_PATH ((xmlChar*)"../registers/register") /**< all standard registers nodes.*/
47
#define BIT_REGISTERS_PATH ((xmlChar*)"./registers_bits/register_bits") /**< all bits registers nodes.*/
49
static char *pcilib_xml_bank_default_format = "0x%lx";
52
pcilib_register_description_t base;
53
pcilib_register_value_t min, max;
54
} pcilib_xml_register_description_t;
57
static xmlNodePtr pcilib_xml_get_parent_bank_node(xmlDocPtr doc, xmlNodePtr node) {
59
bank_node = node->parent->parent;
60
// bank_description is always a first node according to the XSD schema
61
return xmlFirstElementChild(bank_node);
65
static xmlNodePtr pcilib_xml_get_parent_register_node(xmlDocPtr doc, xmlNodePtr node) {
66
return node->parent->parent;
70
static int pcilib_xml_parse_register(pcilib_t *ctx, pcilib_xml_register_description_t *xml_desc, xmlDocPtr doc, xmlNodePtr node, pcilib_register_bank_description_t *bdesc) {
71
pcilib_register_description_t *desc = (pcilib_register_description_t*)xml_desc;
77
for (cur = node->children; cur != NULL; cur = cur->next) {
78
if (!cur->children) continue;
79
if (!xmlNodeIsText(cur->children)) continue;
81
name = (char*)cur->name;
82
value = (char*)cur->children->content;
85
if (!strcasecmp((char*)name, "address")) {
86
uintptr_t addr = strtol(value, &endptr, 0);
87
if ((strlen(endptr) > 0)) {
88
pcilib_error("Invalid address (%s) is specified in the XML register description", value);
89
return PCILIB_ERROR_INVALID_DATA;
92
} else if(!strcasecmp(name, "offset")) {
93
int offset = strtol(value, &endptr, 0);
94
if ((strlen(endptr) > 0)||(offset < 0)||(offset > (8 * sizeof(pcilib_register_value_t)))) {
95
pcilib_error("Invalid offset (%s) is specified in the XML register description", value);
96
return PCILIB_ERROR_INVALID_DATA;
98
desc->offset = (pcilib_register_size_t)offset;
99
} else if (!strcasecmp(name,"size")) {
100
int size = strtol(value, &endptr, 0);
101
if ((strlen(endptr) > 0)||(size <= 0)||(size > (8 * sizeof(pcilib_register_value_t)))) {
102
pcilib_error("Invalid size (%s) is specified in the XML register description", value);
103
return PCILIB_ERROR_INVALID_DATA;
105
desc->bits = (pcilib_register_size_t)size;
106
} else if (!strcasecmp(name, "default")) {
107
pcilib_register_value_t val = strtol(value, &endptr, 0);
108
if ((strlen(endptr) > 0)) {
109
pcilib_error("Invalid default value (%s) is specified in the XML register description", value);
110
return PCILIB_ERROR_INVALID_DATA;
112
desc->defvalue = val;
113
} else if (!strcasecmp(name,"min")) {
114
pcilib_register_value_t min = strtol(value, &endptr, 0);
115
if ((strlen(endptr) > 0)) {
116
pcilib_error("Invalid minimum value (%s) is specified in the XML register description", value);
117
return PCILIB_ERROR_INVALID_DATA;
120
} else if (!strcasecmp(name, "max")) {
121
pcilib_register_value_t max = strtol(value, &endptr, 0);
122
if ((strlen(endptr) > 0)) {
123
pcilib_error("Invalid minimum value (%s) is specified in the XML register description", value);
124
return PCILIB_ERROR_INVALID_DATA;
127
} else if (!strcasecmp((char*)name,"rwmask")) {
128
if (!strcasecmp(value, "all")) {
129
desc->rwmask = PCILIB_REGISTER_ALL_BITS;
130
} else if (!strcasecmp(value, "none")) {
133
uintptr_t mask = strtol(value, &endptr, 0);
134
if ((strlen(endptr) > 0)) {
135
pcilib_error("Invalid mask (%s) is specified in the XML register description", value);
136
return PCILIB_ERROR_INVALID_DATA;
140
} else if (!strcasecmp(name, "mode")) {
141
if (!strcasecmp(value, "R")) {
142
desc->mode = PCILIB_REGISTER_R;
143
} else if (!strcasecmp(value, "W")) {
144
desc->mode = PCILIB_REGISTER_W;
145
} else if (!strcasecmp(value, "RW")) {
146
desc->mode = PCILIB_REGISTER_RW;
147
} else if (!strcasecmp(value, "W")) {
148
desc->mode = PCILIB_REGISTER_W;
149
} else if (!strcasecmp(value, "RW1C")) {
150
desc->mode = PCILIB_REGISTER_RW1C;
151
} else if (!strcasecmp(value, "W1C")) {
152
desc->mode = PCILIB_REGISTER_W1C;
154
pcilib_error("Invalid access mode (%s) is specified in the XML register description", value);
155
return PCILIB_ERROR_INVALID_DATA;
157
} else if (!strcasecmp(name, "type")) {
158
if (!strcasecmp(value, "fifo")) {
159
desc->type = PCILIB_REGISTER_FIFO;
161
pcilib_error("Invalid register type (%s) is specified in the XML register description", value);
162
return PCILIB_ERROR_INVALID_DATA;
164
} else if (!strcasecmp(name,"name")) {
166
} else if (!strcasecmp(name,"description")) {
167
desc->description = value;
174
static int pcilib_xml_create_register(pcilib_t *ctx, pcilib_register_bank_t bank, xmlXPathContextPtr xpath, xmlDocPtr doc, xmlNodePtr node) {
177
xmlXPathObjectPtr nodes;
178
xmlNodeSetPtr nodeset;
180
pcilib_xml_register_description_t desc = {{0}};
181
pcilib_xml_register_description_t fdesc;
183
pcilib_register_t reg;
185
desc.base.bank = ctx->banks[bank].addr;
186
desc.base.rwmask = PCILIB_REGISTER_ALL_BITS;
187
desc.base.mode = PCILIB_REGISTER_R;
188
desc.base.type = PCILIB_REGISTER_STANDARD;
190
err = pcilib_xml_parse_register(ctx, &desc, doc, node, &ctx->banks[bank]);
192
pcilib_error("Error (%i) parsing an XML register", err);
196
err = pcilib_add_registers(ctx, PCILIB_MODEL_MODIFICATION_FLAG_OVERRIDE, 1, &desc.base, ®);
198
pcilib_error("Error (%i) adding a new XML register (%s) to the model", err, desc.base.name);
202
ctx->register_ctx[reg].xml = node;
203
ctx->register_ctx[reg].min = desc.min;
204
ctx->register_ctx[reg].max = desc.max;
207
nodes = xmlXPathEvalExpression(BIT_REGISTERS_PATH, xpath);
211
xmlErrorPtr xmlerr = xmlGetLastError();
213
if (xmlerr) pcilib_error("Failed to parse XPath expression %s, xmlXPathEvalExpression reported error %d - %s", BIT_REGISTERS_PATH, xmlerr->code, xmlerr->message);
214
else pcilib_error("Failed to parse XPath expression %s", BIT_REGISTERS_PATH);
215
return PCILIB_ERROR_FAILED;
218
nodeset = nodes->nodesetval;
219
if (!xmlXPathNodeSetIsEmpty(nodeset)) {
222
for (i = 0; i < nodeset->nodeNr; i++) {
223
memset(&fdesc, 0, sizeof(pcilib_xml_register_description_t));
225
fdesc.base.bank = desc.base.bank;
226
fdesc.base.addr = desc.base.addr;
227
fdesc.base.mode = desc.base.mode;
228
fdesc.base.rwmask = desc.base.rwmask;
229
fdesc.base.type = PCILIB_REGISTER_BITS;
231
err = pcilib_xml_parse_register(ctx, &fdesc, doc, nodeset->nodeTab[i], &ctx->banks[bank]);
233
pcilib_error("Error parsing field in the XML register %s", desc.base.name);
237
err = pcilib_add_registers(ctx, PCILIB_MODEL_MODIFICATION_FLAG_OVERRIDE, 1, &fdesc.base, ®);
239
pcilib_error("Error (%i) adding a new XML register (%s) to the model", err, fdesc.base.name);
243
ctx->register_ctx[reg].xml = nodeset->nodeTab[i];
244
ctx->register_ctx[reg].min = fdesc.min;
245
ctx->register_ctx[reg].max = fdesc.max;
248
xmlXPathFreeObject(nodes);
253
static int pcilib_xml_create_bank(pcilib_t *ctx, xmlXPathContextPtr xpath, xmlDocPtr doc, xmlNodePtr node) {
257
pcilib_register_bank_description_t desc = {0};
258
pcilib_register_bank_t bank;
263
xmlXPathObjectPtr nodes;
264
xmlNodeSetPtr nodeset;
267
desc.format = pcilib_xml_bank_default_format;
268
desc.addr = PCILIB_REGISTER_BANK_DYNAMIC;
269
desc.bar = PCILIB_BAR_NOBAR;
271
desc.protocol = PCILIB_REGISTER_PROTOCOL_DEFAULT;
273
desc.endianess = PCILIB_HOST_ENDIAN;
274
desc.raw_endianess = PCILIB_HOST_ENDIAN;
276
// iterate through all children, representing bank properties, to fill the structure
277
for (cur = node->children; cur != NULL; cur = cur->next) {
278
if (!cur->children) continue;
279
if (!xmlNodeIsText(cur->children)) continue;
281
name = (char*)cur->name;
282
value = (char*)cur->children->content;
283
if (!value) continue;
285
if (!strcasecmp(name, "bar")) {
286
char bar = value[0]-'0';
287
if ((strlen(value) != 1)||(bar < 0)||(bar > 5)) {
288
pcilib_error("Invalid BAR (%s) is specified in the XML bank description", value);
289
return PCILIB_ERROR_INVALID_DATA;
291
desc.bar = (pcilib_bar_t)bar;
293
} else if (!strcasecmp(name,"size")) {
294
long size = strtol(value, &endptr, 0);
295
if ((strlen(endptr) > 0)||(size<=0)) {
296
pcilib_error("Invalid bank size (%s) is specified in the XML bank description", value);
297
return PCILIB_ERROR_INVALID_DATA;
299
desc.size = (size_t)size;
301
} else if (!strcasecmp(name,"protocol")) {
302
pcilib_register_protocol_t protocol = pcilib_find_register_protocol_by_name(ctx, value);
303
if (protocol == PCILIB_REGISTER_PROTOCOL_INVALID) {
304
pcilib_error("Unsupported protocol (%s) is specified in the XML bank description", value);
305
return PCILIB_ERROR_NOTSUPPORTED;
307
desc.protocol = ctx->protocols[protocol].addr;
309
} else if (!strcasecmp(name,"address")) {
310
uintptr_t addr = strtol(value, &endptr, 0);
311
if ((strlen(endptr) > 0)) {
312
pcilib_error("Invalid address (%s) is specified in the XML bank description", value);
313
return PCILIB_ERROR_INVALID_DATA;
315
desc.read_addr = addr;
316
desc.write_addr = addr;
318
} else if (!strcasecmp(name,"read_address")) {
319
uintptr_t addr = strtol(value, &endptr, 0);
320
if ((strlen(endptr) > 0)) {
321
pcilib_error("Invalid address (%s) is specified in the XML bank description", value);
322
return PCILIB_ERROR_INVALID_DATA;
324
desc.read_addr = addr;
326
} else if (!strcasecmp(name,"write_address")) {
327
uintptr_t addr = strtol(value, &endptr, 0);
328
if ((strlen(endptr) > 0)) {
329
pcilib_error("Invalid address (%s) is specified in the XML bank description", value);
330
return PCILIB_ERROR_INVALID_DATA;
332
desc.write_addr = addr;
334
} else if(strcasecmp((char*)name,"word_size")==0) {
335
int access = strtol(value, &endptr, 0);
336
if ((strlen(endptr) > 0)||(access%8)||(access<=0)||(access>(8 * sizeof(pcilib_register_value_t)))) {
337
pcilib_error("Invalid word size (%s) is specified in the XML bank description", value);
338
return PCILIB_ERROR_INVALID_DATA;
340
desc.access = access;
342
} else if (!strcasecmp(name,"endianess")) {
343
if (!strcasecmp(value,"little")) desc.endianess = PCILIB_LITTLE_ENDIAN;
344
else if (!strcasecmp(value,"big")) desc.endianess = PCILIB_BIG_ENDIAN;
345
else if (!strcasecmp(value,"host")) desc.endianess = PCILIB_HOST_ENDIAN;
347
pcilib_error("Invalid endianess (%s) is specified in the XML bank description", value);
348
return PCILIB_ERROR_INVALID_DATA;
351
} else if (!strcasecmp(name,"format")) {
354
} else if (!strcasecmp((char*)name,"name")) {
356
} else if (!strcasecmp((char*)name,"description")) {
357
desc.description = value;
359
} else if (!strcasecmp((char*)name,"override")) {
364
err = pcilib_add_register_banks(ctx, override?PCILIB_MODEL_MODIFICATION_FLAG_OVERRIDE:PCILIB_MODEL_MODIFICATION_FLAG_SKIP_EXISTING, 1, &desc, &bank);
366
pcilib_error("Error adding register bank (%s) specified in the XML bank description", desc.name);
370
ctx->xml.bank_nodes[bank] = node;
371
if (ctx->bank_ctx[bank]) {
372
ctx->bank_ctx[bank]->xml = node;
376
nodes = xmlXPathEvalExpression(REGISTERS_PATH, xpath);
380
xmlErrorPtr xmlerr = xmlGetLastError();
381
if (xmlerr) pcilib_error("Failed to parse XPath expression %s, xmlXPathEvalExpression reported error %d - %s", REGISTERS_PATH, xmlerr->code, xmlerr->message);
382
else pcilib_error("Failed to parse XPath expression %s", REGISTERS_PATH);
383
return PCILIB_ERROR_FAILED;
386
nodeset = nodes->nodesetval;
387
if (!xmlXPathNodeSetIsEmpty(nodeset)) {
389
for (i = 0; i < nodeset->nodeNr; i++) {
390
err = pcilib_xml_create_register(ctx, bank, xpath, doc, nodeset->nodeTab[i]);
391
if (err) pcilib_error("Error creating XML registers for bank %s", desc.name);
394
xmlXPathFreeObject(nodes);
400
/** pcilib_xml_initialize_banks
402
* function to create the structures to store the banks from the AST
403
* @see pcilib_xml_create_bank
404
* @param[in] doc the AST of the xml file.
405
* @param[in] pci the pcilib_t running, which will be filled
407
static int pcilib_xml_process_document(pcilib_t *ctx, xmlDocPtr doc, xmlXPathContextPtr xpath) {
408
xmlXPathObjectPtr bank_nodes;
409
xmlNodeSetPtr nodeset;
411
bank_nodes = xmlXPathEvalExpression(BANKS_PATH, xpath);
413
xmlErrorPtr xmlerr = xmlGetLastError();
414
if (xmlerr) pcilib_error("Failed to parse XPath expression %s, xmlXPathEvalExpression reported error %d - %s", BANKS_PATH, xmlerr->code, xmlerr->message);
415
else pcilib_error("Failed to parse XPath expression %s", BANKS_PATH);
416
return PCILIB_ERROR_FAILED;
420
nodeset = bank_nodes->nodesetval;
421
if (!xmlXPathNodeSetIsEmpty(nodeset)) {
423
for (i = 0; i < nodeset->nodeNr; i++) {
424
pcilib_xml_create_bank(ctx, xpath, doc, nodeset->nodeTab[i]);
427
xmlXPathFreeObject(bank_nodes);
432
static int pcilib_xml_load_xsd(pcilib_t *ctx, char *xsd_filename) {
435
xmlSchemaParserCtxtPtr ctxt;
437
/** we first parse the xsd file for AST with validation*/
438
ctxt = xmlSchemaNewParserCtxt(xsd_filename);
440
xmlErrorPtr xmlerr = xmlGetLastError();
441
if (xmlerr) pcilib_error("xmlSchemaNewParserCtxt reported error %d - %s", xmlerr->code, xmlerr->message);
442
else pcilib_error("Failed to create a parser for XML schemas");
443
return PCILIB_ERROR_FAILED;
446
ctx->xml.schema = xmlSchemaParse(ctxt);
447
if (!ctx->xml.schema) {
448
xmlErrorPtr xmlerr = xmlGetLastError();
449
xmlSchemaFreeParserCtxt(ctxt);
450
if (xmlerr) pcilib_error("Failed to parse XML schema, xmlSchemaParse reported error %d - %s", xmlerr->code, xmlerr->message);
451
else pcilib_error("Failed to parse XML schema");
452
return PCILIB_ERROR_INVALID_DATA;
455
xmlSchemaFreeParserCtxt(ctxt);
457
ctx->xml.validator = xmlSchemaNewValidCtxt(ctx->xml.schema);
458
if (!ctx->xml.validator) {
459
xmlErrorPtr xmlerr = xmlGetLastError();
460
if (xmlerr) pcilib_error("xmlSchemaNewValidCtxt reported error %d - %s", xmlerr->code, xmlerr->message);
461
else pcilib_error("Failed to create a validation context");
462
return PCILIB_ERROR_FAILED;
465
err = xmlSchemaSetValidOptions(ctx->xml.validator, XML_SCHEMA_VAL_VC_I_CREATE);
467
xmlErrorPtr xmlerr = xmlGetLastError();
468
if (xmlerr) pcilib_error("xmlSchemaSetValidOptions reported error %d - %s", xmlerr->code, xmlerr->message);
469
else pcilib_error("Failed to configure the validation context to populate default attributes");
470
return PCILIB_ERROR_FAILED;
476
static int pcilib_xml_load_file(pcilib_t *ctx, const char *path, const char *name) {
480
xmlXPathContextPtr xpath;
482
full_name = (char*)alloca(strlen(path) + strlen(name) + 2);
484
pcilib_error("Error allocating %zu bytes of memory in stack to create a file name", strlen(path) + strlen(name) + 2);
485
return PCILIB_ERROR_MEMORY;
488
sprintf(full_name, "%s/%s", path, name);
490
doc = xmlCtxtReadFile(ctx->xml.parser, full_name, NULL, 0);
492
xmlErrorPtr xmlerr = xmlCtxtGetLastError(ctx->xml.parser);
493
if (xmlerr) pcilib_error("Error parsing %s, xmlCtxtReadFile reported error %d - %s", full_name, xmlerr->code, xmlerr->message);
494
else pcilib_error("Error parsing %s", full_name);
495
return PCILIB_ERROR_INVALID_DATA;
498
err = xmlSchemaValidateDoc(ctx->xml.validator, doc);
500
xmlErrorPtr xmlerr = xmlCtxtGetLastError(ctx->xml.parser);
502
if (xmlerr) pcilib_error("Error validating %s, xmlSchemaValidateDoc reported error %d - %s", full_name, xmlerr->code, xmlerr->message);
503
else pcilib_error("Error validating %s", full_name);
504
return PCILIB_ERROR_VERIFY;
507
xpath = xmlXPathNewContext(doc);
509
xmlErrorPtr xmlerr = xmlGetLastError();
511
if (xmlerr) pcilib_error("Document %s: xmlXpathNewContext reported error %d - %s for document %s", full_name, xmlerr->code, xmlerr->message);
512
else pcilib_error("Error creating XPath context for %s", full_name);
513
return PCILIB_ERROR_FAILED;
516
// This can only partially fail... Therefore we need to keep XML and just return the error...
517
err = pcilib_xml_process_document(ctx, doc, xpath);
519
if (ctx->xml.num_files == PCILIB_MAX_MODEL_FILES) {
521
xmlXPathFreeContext(xpath);
522
pcilib_error("Too many XML files for a model, only up to %zu are supported", PCILIB_MAX_MODEL_FILES);
523
return PCILIB_ERROR_TOOBIG;
526
ctx->xml.docs[ctx->xml.num_files] = doc;
527
ctx->xml.xpath[ctx->xml.num_files] = xpath;
528
ctx->xml.num_files++;
534
int pcilib_process_xml(pcilib_t *ctx, const char *location) {
538
struct dirent *file = NULL;
539
char *model_dir, *model_path;
541
model_dir = getenv("PCILIB_MODEL_DIR");
542
if (!model_dir) model_dir = PCILIB_MODEL_DIR;
544
model_path = (char*)alloca(strlen(model_dir) + strlen(location) + 2);
545
if (!model_path) return PCILIB_ERROR_MEMORY;
547
sprintf(model_path, "%s/%s", model_dir, location);
549
rep = opendir(model_path);
550
if (!rep) return PCILIB_ERROR_NOTFOUND;
552
while ((file = readdir(rep)) != NULL) {
553
size_t len = strlen(file->d_name);
554
if ((len < 4)||(strcasecmp(file->d_name + len - 4, ".xml"))) continue;
555
if (file->d_type != DT_REG) continue;
557
err = pcilib_xml_load_file(ctx, model_path, file->d_name);
558
if (err) pcilib_error("Error processing XML file %s", file->d_name);
568
* this function will initialize the registers and banks from the xml files
569
* @param[in,out] ctx the pciilib_t running that gets filled with structures
570
* @param[in] model the current model of ctx
571
* @return an error code
573
int pcilib_init_xml(pcilib_t *ctx, const char *model) {
581
model_dir = getenv("PCILIB_MODEL_DIR");
582
if (!model_dir) model_dir = PCILIB_MODEL_DIR;
584
xsd_path = (char*)alloca(strlen(model_dir) + 16);
585
if (!xsd_path) return PCILIB_ERROR_MEMORY;
587
sprintf(xsd_path, "%s/model.xsd", model_dir);
588
if (stat(xsd_path, &st)) {
589
pcilib_info("XML models are not present");
590
return PCILIB_ERROR_NOTFOUND;
593
ctx->xml.parser = xmlNewParserCtxt();
594
if (!ctx->xml.parser) {
595
xmlErrorPtr xmlerr = xmlGetLastError();
596
if (xmlerr) pcilib_error("xmlNewParserCtxt reported error %d (%s)", xmlerr->code, xmlerr->message);
597
else pcilib_error("Failed to create an XML parser context");
598
return PCILIB_ERROR_FAILED;
601
err = pcilib_xml_load_xsd(ctx, xsd_path);
604
return pcilib_process_xml(ctx, model);
608
* this function free the xml parts of the pcilib_t running, and some libxml ashes
609
* @param[in] pci the pcilib_t running
611
void pcilib_free_xml(pcilib_t *ctx) {
614
memset(ctx->xml.bank_nodes, 0, sizeof(ctx->xml.bank_nodes));
615
for (i = 0; i < ctx->num_banks; i++) {
616
if (ctx->bank_ctx[i])
617
ctx->bank_ctx[i]->xml = NULL;
620
for (i = 0; i < ctx->num_reg; i++) {
621
ctx->register_ctx[i].xml = NULL;
624
for (i = 0; i < ctx->xml.num_files; i++) {
625
if (ctx->xml.docs[i]) {
626
xmlFreeDoc(ctx->xml.docs[i]);
627
ctx->xml.docs[i] = NULL;
629
if (ctx->xml.xpath[i]) {
630
xmlXPathFreeContext(ctx->xml.xpath[i]);
631
ctx->xml.xpath[i] = NULL;
634
ctx->xml.num_files = 0;
636
if (ctx->xml.validator) {
637
xmlSchemaFreeValidCtxt(ctx->xml.validator);
638
ctx->xml.validator = NULL;
641
if (ctx->xml.schema) {
642
xmlSchemaFree(ctx->xml.schema);
643
ctx->xml.schema = NULL;
647
if (ctx->xml.parser) {
648
xmlFreeParserCtxt(ctx->xml.parser);
649
ctx->xml.parser = NULL;
652
xmlSchemaCleanupTypes();