1 /* ----------------------------------------------------------------------- *
3 * Copyright 2006-2007 Erwan Velu - All Rights Reserved
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation
7 * files (the "Software"), to deal in the Software without
8 * restriction, including without limitation the rights to use,
9 * copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom
11 * the Software is furnished to do so, subject to the following
14 * The above copyright notice and this permission notice shall
15 * be included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 * OTHER DEALINGS IN THE SOFTWARE.
26 * ----------------------------------------------------------------------- */
31 * A module to extract pci informations
42 #include <syslinux/zio.h>
45 # define dprintf printf
47 # define dprintf(...) ((void)0)
52 /* searching the next char that is not a space */
53 static char *skipspace(char *p
)
55 while (*p
&& *p
<= ' ')
61 /* removing any \n found in a string */
62 static void remove_eol(char *string
)
64 int j
= strlen(string
);
66 for (i
= 0; i
< j
; i
++)
67 if (string
[i
] == '\n')
71 /* converting a hexa string into its numerical value */
72 static int hex_to_int(char *hexa
)
74 return strtoul(hexa
, NULL
, 16);
77 /* Replace char 'old' by char 'new' in source */
78 void chr_replace(char *source
, char old
, char new)
82 if (source
[0] == old
) source
[0]=new;
86 /* Try to match any pci device to the appropriate kernel module */
87 /* it uses the modules.pcimap from the boot device */
88 int get_module_name_from_pcimap(struct pci_domain
*domain
,
89 char *modules_pcimap_path
)
92 char module_name
[21]; // the module name field is 21 char long
93 char delims
[]=" "; // colums are separated by spaces
96 char sub_vendor_id
[16];
97 char sub_product_id
[16];
99 struct pci_device
*dev
=NULL
;
101 /* Intializing the linux_kernel_module for each pci device to "unknown" */
102 /* adding a dev_info member if needed */
103 for_each_pci_func(dev
, domain
) {
104 /* initialize the dev_info structure if it doesn't exist yet. */
105 if (! dev
->dev_info
) {
106 dev
->dev_info
= zalloc(sizeof *dev
->dev_info
);
110 for (int i
=0;i
<MAX_KERNEL_MODULES_PER_PCI_DEVICE
;i
++) {
111 if (strlen(dev
->dev_info
->linux_kernel_module
[i
])==0)
112 strlcpy(dev
->dev_info
->linux_kernel_module
[i
], "unknown",7);
116 /* Opening the modules.pcimap (of a linux kernel) from the boot device */
117 f
=zfopen(modules_pcimap_path
, "r");
119 return -ENOMODULESPCIMAP
;
121 strcpy(vendor_id
,"0000");
122 strcpy(product_id
,"0000");
123 strcpy(sub_product_id
,"0000");
124 strcpy(sub_vendor_id
,"0000");
126 /* for each line we found in the modules.pcimap */
127 while ( fgets(line
, sizeof line
, f
) ) {
128 /* skipping unecessary lines */
129 if ((line
[0] == '#') || (line
[0] == ' ') || (line
[0] == 10))
135 /* looking for the next field */
136 result
= strtok(line
, delims
);
137 while( result
!= NULL
) {
138 /* if the column is larger than 1 char */
139 /* multiple spaces generates some empty fields */
140 if (strlen(result
)>1) {
142 /* About case 0, the kernel module name is featuring '_' or '-'
143 * in the module name whereas modules.alias is only using '_'.
144 * To avoid kernel modules duplication, let's rename all '-' in '_'
145 * to match what modules.alias provides */
146 case 0:chr_replace(result
,'-','_');strcpy(module_name
,result
); break;
147 case 1:strcpy(vendor_id
,result
); break;
148 case 2:strcpy(product_id
,result
); break;
149 case 3:strcpy(sub_vendor_id
,result
); break;
150 case 4:strcpy(sub_product_id
,result
); break;
154 /* Searching the next field */
155 result
= strtok( NULL
, delims
);
157 int int_vendor_id
=hex_to_int(vendor_id
);
158 int int_sub_vendor_id
=hex_to_int(sub_vendor_id
);
159 int int_product_id
=hex_to_int(product_id
);
160 int int_sub_product_id
=hex_to_int(sub_product_id
);
161 /* if a pci_device matches an entry, fill the linux_kernel_module with
162 the appropriate kernel module */
163 for_each_pci_func(dev
, domain
) {
164 if (int_vendor_id
== dev
->vendor
&&
165 int_product_id
== dev
->product
&&
166 (int_sub_product_id
& dev
->sub_product
)
167 == dev
->sub_product
&&
168 (int_sub_vendor_id
& dev
->sub_vendor
)
169 == dev
->sub_vendor
) {
172 /* Scan all known kernel modules for this pci device */
173 for (int i
=0; i
<dev
->dev_info
->linux_kernel_module_count
; i
++) {
175 /* Try to detect if we already knew the same kernel module*/
176 if (strstr(dev
->dev_info
->linux_kernel_module
[i
], module_name
)) {
181 /* If we don't have this kernel module, let's add it */
183 strcpy(dev
->dev_info
->linux_kernel_module
[dev
->dev_info
->linux_kernel_module_count
], module_name
);
184 dev
->dev_info
->linux_kernel_module_count
++;
193 /* Try to match any pci device to the appropriate class name */
194 /* it uses the pci.ids from the boot device */
195 int get_class_name_from_pci_ids(struct pci_domain
*domain
, char *pciids_path
)
198 char class_name
[PCI_CLASS_NAME_SIZE
];
199 char sub_class_name
[PCI_CLASS_NAME_SIZE
];
200 char class_id_str
[5];
201 char sub_class_id_str
[5];
203 struct pci_device
*dev
;
204 bool class_mode
= false;
206 /* Intializing the vendor/product name for each pci device to "unknown" */
207 /* adding a dev_info member if needed */
208 for_each_pci_func(dev
, domain
) {
209 /* initialize the dev_info structure if it doesn't exist yet. */
210 if (!dev
->dev_info
) {
211 dev
->dev_info
= zalloc(sizeof *dev
->dev_info
);
215 strlcpy(dev
->dev_info
->class_name
, "unknown", 7);
218 /* Opening the pci.ids from the boot device */
219 f
= zfopen(pciids_path
, "r");
223 /* for each line we found in the pci.ids */
224 while (fgets(line
, sizeof line
, f
)) {
225 /* Skipping uncessary lines */
226 if ((line
[0] == '#') || (line
[0] == ' ') || (line
[0] == 10))
229 /* Until we found a line starting with a 'C', we are not parsing classes */
232 if (class_mode
== false)
234 strlcpy(class_name
, "unknown", 7);
235 /* If the line doesn't start with a tab, it means that's a class name */
236 if (line
[0] != '\t') {
238 /* ignore the two first char and then copy 2 chars (class id) */
239 strlcpy(class_id_str
, &line
[2], 2);
242 /* the class name is the next field */
243 strlcpy(class_name
, skipspace(strstr(line
, " ")),
244 PCI_CLASS_NAME_SIZE
- 1);
245 remove_eol(class_name
);
247 int int_class_id_str
= hex_to_int(class_id_str
);
248 /* assign the class_name to any matching pci device */
249 for_each_pci_func(dev
, domain
) {
250 if (int_class_id_str
== dev
->class[2]) {
251 strlcpy(dev
->dev_info
->class_name
, class_name
,
252 PCI_CLASS_NAME_SIZE
- 1);
253 /* This value is usually the main category */
254 strlcpy(dev
->dev_info
->category_name
, class_name
+ 4,
255 PCI_CLASS_NAME_SIZE
- 1);
258 /* if we have a tab + a char, it means this is a sub class name */
259 } else if ((line
[0] == '\t') && (line
[1] != '\t')) {
261 /* the sub class name the second field */
262 strlcpy(sub_class_name
, skipspace(strstr(line
, " ")),
263 PCI_CLASS_NAME_SIZE
- 1);
264 remove_eol(sub_class_name
);
266 /* the sub class id is first field */
267 strlcpy(sub_class_id_str
, &line
[1], 2);
268 sub_class_id_str
[2] = 0;
270 int int_class_id_str
= hex_to_int(class_id_str
);
271 int int_sub_class_id_str
= hex_to_int(sub_class_id_str
);
272 /* assign the product_name to any matching pci device */
273 for_each_pci_func(dev
, domain
) {
274 if (int_class_id_str
== dev
->class[2] &&
275 int_sub_class_id_str
== dev
->class[1])
276 strlcpy(dev
->dev_info
->class_name
, sub_class_name
,
277 PCI_CLASS_NAME_SIZE
- 1);
286 /* Try to match any pci device to the appropriate vendor and product name */
287 /* it uses the pci.ids from the boot device */
288 int get_name_from_pci_ids(struct pci_domain
*domain
, char *pciids_path
)
291 char vendor
[PCI_VENDOR_NAME_SIZE
];
293 char product
[PCI_PRODUCT_NAME_SIZE
];
295 char sub_product_id
[5];
296 char sub_vendor_id
[5];
298 struct pci_device
*dev
;
299 bool skip_to_next_vendor
= false;
300 uint16_t int_vendor_id
;
301 uint16_t int_product_id
;
302 uint16_t int_sub_product_id
;
303 uint16_t int_sub_vendor_id
;
305 /* Intializing the vendor/product name for each pci device to "unknown" */
306 /* adding a dev_info member if needed */
307 for_each_pci_func(dev
, domain
) {
308 /* initialize the dev_info structure if it doesn't exist yet. */
309 if (!dev
->dev_info
) {
310 dev
->dev_info
= zalloc(sizeof *dev
->dev_info
);
314 strlcpy(dev
->dev_info
->vendor_name
, "unknown", 7);
315 strlcpy(dev
->dev_info
->product_name
, "unknown", 7);
318 /* Opening the pci.ids from the boot device */
319 f
= zfopen(pciids_path
, "r");
323 strlcpy(vendor_id
, "0000", 4);
324 strlcpy(product_id
, "0000", 4);
325 strlcpy(sub_product_id
, "0000", 4);
326 strlcpy(sub_vendor_id
, "0000", 4);
328 /* for each line we found in the pci.ids */
329 while (fgets(line
, sizeof line
, f
)) {
330 /* Skipping uncessary lines */
331 if ((line
[0] == '#') || (line
[0] == ' ') || (line
[0] == 'C') ||
335 /* If the line doesn't start with a tab, it means that's a vendor id */
336 if (line
[0] != '\t') {
338 /* the 4 first chars are the vendor_id */
339 strlcpy(vendor_id
, line
, 4);
341 /* the vendor name is the next field */
343 strlcpy(vendor
, skipspace(strstr(line
, " ")),
344 PCI_VENDOR_NAME_SIZE
- 1);
347 /* init product_id, sub_product and sub_vendor */
348 strlcpy(product_id
, "0000", 4);
349 strlcpy(sub_product_id
, "0000", 4);
350 strlcpy(sub_vendor_id
, "0000", 4);
352 /* Unless we found a matching device, we have to skip to the next vendor */
353 skip_to_next_vendor
= true;
355 int_vendor_id
= hex_to_int(vendor_id
);
356 /* Iterate in all pci devices to find a matching vendor */
357 for_each_pci_func(dev
, domain
) {
358 /* if one device that match this vendor */
359 if (int_vendor_id
== dev
->vendor
) {
360 /* copy the vendor name for this device */
361 strlcpy(dev
->dev_info
->vendor_name
, vendor
,
362 PCI_VENDOR_NAME_SIZE
- 1);
363 /* Some pci devices match this vendor, so we have to found them */
364 skip_to_next_vendor
= false;
365 /* Let's loop on the other devices as some may have the same vendor */
368 /* if we have a tab + a char, it means this is a product id
369 * but we only look at it if we own some pci devices of the current vendor*/
370 } else if ((line
[0] == '\t') && (line
[1] != '\t')
371 && (skip_to_next_vendor
== false)) {
373 /* the product name the second field */
374 strlcpy(product
, skipspace(strstr(line
, " ")),
375 PCI_PRODUCT_NAME_SIZE
- 1);
378 /* the product id is first field */
379 strlcpy(product_id
, &line
[1], 4);
382 /* init sub_product and sub_vendor */
383 strlcpy(sub_product_id
, "0000", 4);
384 strlcpy(sub_vendor_id
, "0000", 4);
386 int_vendor_id
= hex_to_int(vendor_id
);
387 int_product_id
= hex_to_int(product_id
);
388 /* assign the product_name to any matching pci device */
389 for_each_pci_func(dev
, domain
) {
390 if (int_vendor_id
== dev
->vendor
&&
391 int_product_id
== dev
->product
) {
392 strlcpy(dev
->dev_info
->vendor_name
, vendor
,
393 PCI_VENDOR_NAME_SIZE
- 1);
394 strlcpy(dev
->dev_info
->product_name
, product
,
395 PCI_PRODUCT_NAME_SIZE
- 1);
399 /* if we have two tabs, it means this is a sub product
400 * but we only look at it if we own some pci devices of the current vendor*/
401 } else if ((line
[0] == '\t') && (line
[1] == '\t')
402 && (skip_to_next_vendor
== false)) {
404 /* the product name is last field */
405 strlcpy(product
, skipspace(strstr(line
, " ")),
406 PCI_PRODUCT_NAME_SIZE
- 1);
407 strlcpy(product
, skipspace(strstr(product
, " ")),
408 PCI_PRODUCT_NAME_SIZE
- 1);
411 /* the sub_vendor id is first field */
412 strlcpy(sub_vendor_id
, &line
[2], 4);
413 sub_vendor_id
[4] = 0;
415 /* the sub_vendor id is second field */
416 strlcpy(sub_product_id
, &line
[7], 4);
417 sub_product_id
[4] = 0;
419 int_vendor_id
= hex_to_int(vendor_id
);
420 int_sub_vendor_id
= hex_to_int(sub_vendor_id
);
421 int_product_id
= hex_to_int(product_id
);
422 int_sub_product_id
= hex_to_int(sub_product_id
);
423 /* assign the product_name to any matching pci device */
424 for_each_pci_func(dev
, domain
) {
425 if (int_vendor_id
== dev
->vendor
&&
426 int_product_id
== dev
->product
&&
427 int_sub_product_id
== dev
->sub_product
&&
428 int_sub_vendor_id
== dev
->sub_vendor
) {
429 strlcpy(dev
->dev_info
->vendor_name
, vendor
,
430 PCI_VENDOR_NAME_SIZE
- 1);
431 strlcpy(dev
->dev_info
->product_name
, product
,
432 PCI_PRODUCT_NAME_SIZE
- 1);
441 /* searching if any pcidevice match our query */
442 struct match
*find_pci_device(const struct pci_domain
*domain
,
447 const struct pci_device
*dev
;
449 /* for all matches we have to search */
450 for (m
= list
; m
; m
= m
->next
) {
451 /* for each pci device we know */
452 for_each_pci_func(dev
, domain
) {
453 /* sid & did are the easiest way to compare devices */
454 /* they are made of vendor/product subvendor/subproduct ids */
455 sid
= dev
->svid_sdid
;
457 /* if the current device match */
458 if (((did
^ m
->did
) & m
->did_mask
) == 0 &&
459 ((sid
^ m
->sid
) & m
->sid_mask
) == 0 &&
460 dev
->revision
>= m
->rid_min
&& dev
->revision
<= m
->rid_max
) {
462 ("PCI Match: Vendor=%04x Product=%04x Sub_vendor=%04x Sub_Product=%04x Release=%02x\n",
463 dev
->vendor
, dev
->product
, dev
->sub_vendor
,
464 dev
->sub_product
, dev
->revision
);
465 /* returning the matched pci device */
473 /* scanning the pci bus to find pci devices */
474 struct pci_domain
*pci_scan(void)
476 struct pci_domain
*domain
= NULL
;
477 struct pci_bus
*bus
= NULL
;
478 struct pci_slot
*slot
= NULL
;
479 struct pci_device
*func
= NULL
;
480 unsigned int nbus
, ndev
, nfunc
, maxfunc
;
481 uint32_t did
, sid
, rcid
;
486 cfgtype
= pci_set_config_type(PCI_CFG_AUTO
);
488 dprintf("PCI configuration type %d\n", cfgtype
);
490 if (cfgtype
== PCI_CFG_NONE
)
493 dprintf("Scanning PCI Buses\n");
495 for (nbus
= 0; nbus
< MAX_PCI_BUSES
; nbus
++) {
496 dprintf("Probing bus 0x%02x... \n", nbus
);
499 for (ndev
= 0; ndev
< MAX_PCI_DEVICES
; ndev
++) {
500 maxfunc
= 1; /* Assume a single-function device */
503 for (nfunc
= 0; nfunc
< maxfunc
; nfunc
++) {
504 a
= pci_mkaddr(nbus
, ndev
, nfunc
, 0);
507 if (did
== 0xffffffff || did
== 0xffff0000 ||
508 did
== 0x0000ffff || did
== 0x00000000)
511 hdrtype
= pci_readb(a
+ 0x0e);
514 maxfunc
= MAX_PCI_FUNC
; /* Multifunction device */
516 rcid
= pci_readl(a
+ 0x08);
517 sid
= pci_readl(a
+ 0x2c);
520 domain
= zalloc(sizeof *domain
);
525 bus
= zalloc(sizeof *bus
);
528 domain
->bus
[nbus
] = bus
;
531 slot
= zalloc(sizeof *slot
);
534 bus
->slot
[ndev
] = slot
;
536 func
= zalloc(sizeof *func
);
540 slot
->func
[nfunc
] = func
;
543 func
->svid_sdid
= sid
;
544 func
->rid_class
= rcid
;
547 ("Scanning: BUS %02x DID %08x (%04x:%04x) SID %08x RID %02x\n",
548 nbus
, did
, did
>> 16, (did
<< 16) >> 16, sid
, rcid
& 0xff);
556 free_pci_domain(domain
);
560 /* gathering additional configuration*/
561 void gather_additional_pci_config(struct pci_domain
*domain
)
563 struct pci_device
*dev
;
567 cfgtype
= pci_set_config_type(PCI_CFG_AUTO
);
568 if (cfgtype
== PCI_CFG_NONE
)
571 for_each_pci_func3(dev
, domain
, pci_addr
) {
572 if (!dev
->dev_info
) {
573 dev
->dev_info
= zalloc(sizeof *dev
->dev_info
);
574 if (!dev
->dev_info
) {
578 dev
->dev_info
->irq
= pci_readb(pci_addr
+ 0x3c);
579 dev
->dev_info
->latency
= pci_readb(pci_addr
+ 0x0d);
583 void free_pci_domain(struct pci_domain
*domain
)
586 struct pci_slot
*slot
;
587 struct pci_device
*func
;
588 unsigned int nbus
, ndev
, nfunc
;
591 for (nbus
= 0; nbus
< MAX_PCI_BUSES
; nbus
++) {
592 bus
= domain
->bus
[nbus
];
594 for (ndev
= 0; ndev
< MAX_PCI_DEVICES
; ndev
++) {
595 slot
= bus
->slot
[ndev
];
597 for (nfunc
= 0; nfunc
< MAX_PCI_FUNC
; nfunc
++) {
598 func
= slot
->func
[nfunc
];
601 free(func
->dev_info
);
615 /* Try to match any pci device to the appropriate kernel module */
616 /* it uses the modules.alias from the boot device */
617 int get_module_name_from_alias(struct pci_domain
*domain
, char *modules_alias_path
)
620 char module_name
[21]; // the module name field is 21 char long
621 char delims
[]="*"; // colums are separated by spaces
624 char sub_vendor_id
[16];
625 char sub_product_id
[16];
627 struct pci_device
*dev
=NULL
;
629 /* Intializing the linux_kernel_module for each pci device to "unknown" */
630 /* adding a dev_info member if needed */
631 for_each_pci_func(dev
, domain
) {
632 /* initialize the dev_info structure if it doesn't exist yet. */
633 if (! dev
->dev_info
) {
634 dev
->dev_info
= zalloc(sizeof *dev
->dev_info
);
638 for (int i
=0;i
<MAX_KERNEL_MODULES_PER_PCI_DEVICE
;i
++) {
639 if (strlen(dev
->dev_info
->linux_kernel_module
[i
])==0)
640 strlcpy(dev
->dev_info
->linux_kernel_module
[i
], "unknown",7);
644 /* Opening the modules.pcimap (of a linux kernel) from the boot device */
645 f
=zfopen(modules_alias_path
, "r");
647 return -ENOMODULESALIAS
;
649 /* for each line we found in the modules.pcimap */
650 while ( fgets(line
, sizeof line
, f
) ) {
651 /* skipping unecessary lines */
652 if ((line
[0] == '#') || (strstr(line
,"alias pci:v")==NULL
))
655 /* Resetting temp buffer*/
656 memset(module_name
,0,sizeof(module_name
));
657 memset(vendor_id
,0,sizeof(vendor_id
));
658 memset(sub_vendor_id
,0,sizeof(sub_vendor_id
));
659 memset(product_id
,0,sizeof(product_id
));
660 memset(sub_product_id
,0,sizeof(sub_product_id
));
661 strcpy(vendor_id
,"0000");
662 strcpy(product_id
,"0000");
663 /* ffff will be used to match any device as in modules.alias
664 * a missing subvendor/product have to be considered as 0xFFFF*/
665 strcpy(sub_product_id
,"ffff");
666 strcpy(sub_vendor_id
,"ffff");
671 /* looking for the next field */
672 result
= strtok(line
+strlen("alias pci:v"), delims
);
673 while( result
!= NULL
) {
676 /* Searching for the vendor separator*/
677 char *temp
= strstr(result
,"d");
679 strncpy(vendor_id
,result
,temp
-result
);
680 result
+=strlen(vendor_id
)+1;
683 /* Searching for the product separator*/
684 temp
= strstr(result
,"sv");
686 strncpy(product_id
,result
,temp
-result
);
687 result
+=strlen(product_id
)+1;
690 /* Searching for the sub vendor separator*/
691 temp
= strstr(result
,"sd");
693 strncpy(sub_vendor_id
,result
,temp
-result
);
694 result
+=strlen(sub_vendor_id
)+1;
697 /* Searching for the sub product separator*/
698 temp
= strstr(result
,"bc");
700 strncpy(sub_product_id
,result
,temp
-result
);
701 result
+=strlen(sub_product_id
)+1;
703 /* That's the module name */
704 } else if ((strlen(result
)>2) &&
706 strcpy(module_name
,result
+1);
707 /* We have to replace \n by \0*/
708 module_name
[strlen(module_name
)-1]='\0';
711 /* Searching the next field */
712 result
= strtok( NULL
, delims
);
715 /* Now we have extracted informations from the modules.alias
716 * Let's compare it with the devices we know*/
717 int int_vendor_id
=hex_to_int(vendor_id
);
718 int int_sub_vendor_id
=hex_to_int(sub_vendor_id
);
719 int int_product_id
=hex_to_int(product_id
);
720 int int_sub_product_id
=hex_to_int(sub_product_id
);
721 /* if a pci_device matches an entry, fill the linux_kernel_module with
722 the appropriate kernel module */
723 for_each_pci_func(dev
, domain
) {
724 if (int_vendor_id
== dev
->vendor
&&
725 int_product_id
== dev
->product
&&
726 (int_sub_product_id
& dev
->sub_product
)
727 == dev
->sub_product
&&
728 (int_sub_vendor_id
& dev
->sub_vendor
)
729 == dev
->sub_vendor
) {
732 /* Scan all known kernel modules for this pci device */
733 for (int i
=0; i
<dev
->dev_info
->linux_kernel_module_count
; i
++) {
735 /* Try to detect if we already knew the same kernel module*/
736 if (strstr(dev
->dev_info
->linux_kernel_module
[i
], module_name
)) {
741 /* If we don't have this kernel module, let's add it */
743 strcpy(dev
->dev_info
->linux_kernel_module
[dev
->dev_info
->linux_kernel_module_count
], module_name
);
744 dev
->dev_info
->linux_kernel_module_count
++;