| 1 | |
| 2 | |
| 3 | * Support routines for initializing a PCI subsystem
|
| 4 | *
|
| 5 | * Extruded from code written by
|
| 6 | * Dave Rusling ([email protected])
|
| 7 | * David Mosberger ([email protected])
|
| 8 | * David Miller ([email protected])
|
| 9 | *
|
| 10 | * Fixed for multiple PCI buses, 1999 Andrea Arcangeli <[email protected]>
|
| 11 | *
|
| 12 | * Nov 2000, Ivan Kokshaysky <[email protected]>
|
| 13 | * Resource sorting
|
| 14 |
|
| 15 |
|
| 16 | #include <linux/kernel.h>
|
| 17 | #include <linux/export.h>
|
| 18 | #include <linux/pci.h>
|
| 19 | #include <linux/errno.h>
|
| 20 | #include <linux/ioport.h>
|
| 21 | #include <linux/cache.h>
|
| 22 | #include <linux/slab.h>
|
| 23 | #include "pci.h"
|
| 24 |
|
| 25 | static void pci_std_update_resource(struct pci_dev *dev, int resno)
|
| 26 | {
|
| 27 | struct pci_bus_region region;
|
| 28 | bool disable;
|
| 29 | u16 cmd;
|
| 30 | u32 new, check, mask;
|
| 31 | int reg;
|
| 32 | struct resource *res = pci_resource_n(dev, resno);
|
| 33 | const char *res_name = pci_resource_name(dev, resno);
|
| 34 |
|
| 35 |
|
| 36 | if (dev->is_virtfn)
|
| 37 | return;
|
| 38 |
|
| 39 | |
| 40 | * Ignore resources for unimplemented BARs and unused resource slots
|
| 41 | * for 64 bit BARs.
|
| 42 |
|
| 43 | if (!res->flags)
|
| 44 | return;
|
| 45 |
|
| 46 | if (res->flags & IORESOURCE_UNSET)
|
| 47 | return;
|
| 48 |
|
| 49 | |
| 50 | * Ignore non-moveable resources. This might be legacy resources for
|
| 51 | * which no functional BAR register exists or another important
|
| 52 | * system resource we shouldn't move around.
|
| 53 |
|
| 54 | if (res->flags & IORESOURCE_PCI_FIXED)
|
| 55 | return;
|
| 56 |
|
| 57 | pcibios_resource_to_bus(dev->bus, ®ion, res);
|
| 58 | new = region.start;
|
| 59 |
|
| 60 | if (res->flags & IORESOURCE_IO) {
|
| 61 | mask = (u32)PCI_BASE_ADDRESS_IO_MASK;
|
| 62 | new |= res->flags & ~PCI_BASE_ADDRESS_IO_MASK;
|
| 63 | } else if (resno == PCI_ROM_RESOURCE) {
|
| 64 | mask = PCI_ROM_ADDRESS_MASK;
|
| 65 | } else {
|
| 66 | mask = (u32)PCI_BASE_ADDRESS_MEM_MASK;
|
| 67 | new |= res->flags & ~PCI_BASE_ADDRESS_MEM_MASK;
|
| 68 | }
|
| 69 |
|
| 70 | if (resno < PCI_ROM_RESOURCE) {
|
| 71 | reg = PCI_BASE_ADDRESS_0 + 4 * resno;
|
| 72 | } else if (resno == PCI_ROM_RESOURCE) {
|
| 73 |
|
| 74 | |
| 75 | * Apparently some Matrox devices have ROM BARs that read
|
| 76 | * as zero when disabled, so don't update ROM BARs unless
|
| 77 | * they're enabled. See
|
| 78 | * https://lore.kernel.org/r/[email protected]/
|
| 79 | * But we must update ROM BAR for buggy devices where even a
|
| 80 | * disabled ROM can conflict with other BARs.
|
| 81 |
|
| 82 | if (!(res->flags & IORESOURCE_ROM_ENABLE) &&
|
| 83 | !dev->rom_bar_overlap)
|
| 84 | return;
|
| 85 |
|
| 86 | reg = dev->rom_base_reg;
|
| 87 | if (res->flags & IORESOURCE_ROM_ENABLE)
|
| 88 | new |= PCI_ROM_ADDRESS_ENABLE;
|
| 89 | } else
|
| 90 | return;
|
| 91 |
|
| 92 | |
| 93 | * We can't update a 64-bit BAR atomically, so when possible,
|
| 94 | * disable decoding so that a half-updated BAR won't conflict
|
| 95 | * with another device.
|
| 96 |
|
| 97 | disable = (res->flags & IORESOURCE_MEM_64) && !dev->mmio_always_on;
|
| 98 | if (disable) {
|
| 99 | pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
| 100 | pci_write_config_word(dev, PCI_COMMAND,
|
| 101 | cmd & ~PCI_COMMAND_MEMORY);
|
| 102 | }
|
| 103 |
|
| 104 | pci_write_config_dword(dev, reg, new);
|
| 105 | dev->saved_config_space[reg / 4] = new;
|
| 106 | pci_read_config_dword(dev, reg, &check);
|
| 107 |
|
| 108 | if ((new ^ check) & mask) {
|
| 109 | pci_err(dev, "%s: error updating (%#010x != %#010x)\n",
|
| 110 | res_name, new, check);
|
| 111 | }
|
| 112 |
|
| 113 | if (res->flags & IORESOURCE_MEM_64) {
|
| 114 | new = region.start >> 16 >> 16;
|
| 115 | pci_write_config_dword(dev, reg + 4, new);
|
| 116 | dev->saved_config_space[(reg + 4) / 4] = new;
|
| 117 | pci_read_config_dword(dev, reg + 4, &check);
|
| 118 | if (check != new) {
|
| 119 | pci_err(dev, "%s: error updating (high %#010x != %#010x)\n",
|
| 120 | res_name, new, check);
|
| 121 | }
|
| 122 | }
|
| 123 |
|
| 124 | if (disable)
|
| 125 | pci_write_config_word(dev, PCI_COMMAND, cmd);
|
| 126 | }
|
| 127 |
|
| 128 | void pci_update_resource(struct pci_dev *dev, int resno)
|
| 129 | {
|
| 130 | if (resno <= PCI_ROM_RESOURCE)
|
| 131 | pci_std_update_resource(dev, resno);
|
| 132 | else if (pci_resource_is_iov(resno))
|
| 133 | pci_iov_update_resource(dev, resno);
|
| 134 | }
|
| 135 |
|
| 136 | int pci_claim_resource(struct pci_dev *dev, int resource)
|
| 137 | {
|
| 138 | struct resource *res = &dev->resource[resource];
|
| 139 | const char *res_name = pci_resource_name(dev, resource);
|
| 140 | struct resource *root, *conflict;
|
| 141 |
|
| 142 | if (res->flags & IORESOURCE_UNSET) {
|
| 143 | pci_info(dev, "%s %pR: can't claim; no address assigned\n",
|
| 144 | res_name, res);
|
| 145 | return -EINVAL;
|
| 146 | }
|
| 147 |
|
| 148 | |
| 149 | * If we have a shadow copy in RAM, the PCI device doesn't respond
|
| 150 | * to the shadow range, so we don't need to claim it, and upstream
|
| 151 | * bridges don't need to route the range to the device.
|
| 152 |
|
| 153 | if (res->flags & IORESOURCE_ROM_SHADOW)
|
| 154 | return 0;
|
| 155 |
|
| 156 | root = pci_find_parent_resource(dev, res);
|
| 157 | if (!root) {
|
| 158 | pci_info(dev, "%s %pR: can't claim; no compatible bridge window\n",
|
| 159 | res_name, res);
|
| 160 | res->flags |= IORESOURCE_UNSET;
|
| 161 | return -EINVAL;
|
| 162 | }
|
| 163 |
|
| 164 | conflict = request_resource_conflict(root, res);
|
| 165 | if (conflict) {
|
| 166 | pci_info(dev, "%s %pR: can't claim; address conflict with %s %pR\n",
|
| 167 | res_name, res, conflict->name, conflict);
|
| 168 | res->flags |= IORESOURCE_UNSET;
|
| 169 | return -EBUSY;
|
| 170 | }
|
| 171 |
|
| 172 | pci_dbg(dev, "%s %pR: claiming\n", res_name, res);
|
| 173 |
|
| 174 | return 0;
|
| 175 | }
|
| 176 | EXPORT_SYMBOL(pci_claim_resource);
|
| 177 |
|
| 178 | void pci_disable_bridge_window(struct pci_dev *dev)
|
| 179 | {
|
| 180 |
|
| 181 | pci_write_config_dword(dev, PCI_MEMORY_BASE, 0x0000fff0);
|
| 182 |
|
| 183 |
|
| 184 | pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32, 0);
|
| 185 | pci_write_config_dword(dev, PCI_PREF_MEMORY_BASE, 0x0000fff0);
|
| 186 | pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0xffffffff);
|
| 187 | }
|
| 188 |
|
| 189 | |
| 190 | * Generic function that returns a value indicating that the device's
|
| 191 | * original BIOS BAR address was not saved and so is not available for
|
| 192 | * reinstatement.
|
| 193 | *
|
| 194 | * Can be over-ridden by architecture specific code that implements
|
| 195 | * reinstatement functionality rather than leaving it disabled when
|
| 196 | * normal allocation attempts fail.
|
| 197 |
|
| 198 | resource_size_t __weak pcibios_retrieve_fw_addr(struct pci_dev *dev, int idx)
|
| 199 | {
|
| 200 | return 0;
|
| 201 | }
|
| 202 |
|
| 203 | static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev,
|
| 204 | int resno, resource_size_t size)
|
| 205 | {
|
| 206 | struct resource *root, *conflict;
|
| 207 | resource_size_t fw_addr, start, end;
|
| 208 | const char *res_name = pci_resource_name(dev, resno);
|
| 209 |
|
| 210 | fw_addr = pcibios_retrieve_fw_addr(dev, resno);
|
| 211 | if (!fw_addr)
|
| 212 | return -ENOMEM;
|
| 213 |
|
| 214 | start = res->start;
|
| 215 | end = res->end;
|
| 216 | resource_set_range(res, fw_addr, size);
|
| 217 | res->flags &= ~IORESOURCE_UNSET;
|
| 218 |
|
| 219 | root = pci_find_parent_resource(dev, res);
|
| 220 | if (!root) {
|
| 221 | |
| 222 | * If dev is behind a bridge, accesses will only reach it
|
| 223 | * if res is inside the relevant bridge window.
|
| 224 |
|
| 225 | if (pci_upstream_bridge(dev))
|
| 226 | return -ENXIO;
|
| 227 |
|
| 228 | |
| 229 | * On the root bus, assume the host bridge will forward
|
| 230 | * everything.
|
| 231 |
|
| 232 | if (res->flags & IORESOURCE_IO)
|
| 233 | root = &ioport_resource;
|
| 234 | else
|
| 235 | root = &iomem_resource;
|
| 236 | }
|
| 237 |
|
| 238 | pci_info(dev, "%s: trying firmware assignment %pR\n", res_name, res);
|
| 239 | conflict = request_resource_conflict(root, res);
|
| 240 | if (conflict) {
|
| 241 | pci_info(dev, "%s %pR: conflicts with %s %pR\n", res_name, res,
|
| 242 | conflict->name, conflict);
|
| 243 | res->start = start;
|
| 244 | res->end = end;
|
| 245 | res->flags |= IORESOURCE_UNSET;
|
| 246 | return -EBUSY;
|
| 247 | }
|
| 248 | return 0;
|
| 249 | }
|
| 250 |
|
| 251 | resource_size_t pci_resource_alignment(const struct pci_dev *dev,
|
| 252 | const struct resource *res)
|
| 253 | {
|
| 254 | int resno = pci_resource_num(dev, res);
|
| 255 |
|
| 256 | if (pci_resource_is_iov(resno))
|
| 257 | return pci_sriov_resource_alignment(dev, resno);
|
| 258 | if (dev->class >> 8 == PCI_CLASS_BRIDGE_CARDBUS)
|
| 259 | return pci_cardbus_resource_alignment(res);
|
| 260 | return resource_alignment(res);
|
| 261 | }
|
| 262 |
|
| 263 | |
| 264 | * For mem bridge windows, try to relocate tail remainder space to space
|
| 265 | * before res->start if there's enough free space there. This enables
|
| 266 | * tighter packing for resources.
|
| 267 |
|
| 268 | resource_size_t pci_align_resource(struct pci_dev *dev,
|
| 269 | const struct resource *res,
|
| 270 | const struct resource *empty_res,
|
| 271 | resource_size_t size,
|
| 272 | resource_size_t align)
|
| 273 | {
|
| 274 | resource_size_t remainder, start_addr;
|
| 275 |
|
| 276 | if (!(res->flags & IORESOURCE_MEM))
|
| 277 | return res->start;
|
| 278 |
|
| 279 | if (IS_ALIGNED(size, align))
|
| 280 | return res->start;
|
| 281 |
|
| 282 | remainder = size - ALIGN_DOWN(size, align);
|
| 283 |
|
| 284 | if (!IS_ALIGNED(remainder, pci_min_window_alignment(dev->bus, res->flags)))
|
| 285 | return res->start;
|
| 286 |
|
| 287 | if (res->start < remainder)
|
| 288 | return res->start;
|
| 289 | start_addr = res->start - remainder;
|
| 290 | if (empty_res->start > start_addr)
|
| 291 | return res->start;
|
| 292 |
|
| 293 | pci_dbg(dev, "%pR: moving candidate start address below align to %llx\n",
|
| 294 | res, (unsigned long long)start_addr);
|
| 295 | return start_addr;
|
| 296 | }
|
| 297 |
|
| 298 | |
| 299 | * We don't have to worry about legacy ISA devices, so nothing to do here.
|
| 300 | * This is marked as __weak because multiple architectures define it; it should
|
| 301 | * eventually go away.
|
| 302 |
|
| 303 | resource_size_t __weak pcibios_align_resource(void *data,
|
| 304 | const struct resource *res,
|
| 305 | const struct resource *empty_res,
|
| 306 | resource_size_t size,
|
| 307 | resource_size_t align)
|
| 308 | {
|
| 309 | struct pci_dev *dev = data;
|
| 310 |
|
| 311 | return pci_align_resource(dev, res, empty_res, size, align);
|
| 312 | }
|
| 313 |
|
| 314 | static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev,
|
| 315 | int resno, resource_size_t size, resource_size_t align)
|
| 316 | {
|
| 317 | struct resource *res = pci_resource_n(dev, resno);
|
| 318 | resource_size_t min;
|
| 319 | int ret;
|
| 320 |
|
| 321 | min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM;
|
| 322 |
|
| 323 | |
| 324 | * First, try exact prefetching match. Even if a 64-bit
|
| 325 | * prefetchable bridge window is below 4GB, we can't put a 32-bit
|
| 326 | * prefetchable resource in it because pbus_size_mem() assumes a
|
| 327 | * 64-bit window will contain no 32-bit resources. If we assign
|
| 328 | * things differently than they were sized, not everything will fit.
|
| 329 |
|
| 330 | ret = pci_bus_alloc_resource(bus, res, size, align, min,
|
| 331 | IORESOURCE_PREFETCH | IORESOURCE_MEM_64,
|
| 332 | pcibios_align_resource, dev);
|
| 333 | if (ret == 0)
|
| 334 | return 0;
|
| 335 |
|
| 336 | |
| 337 | * If the prefetchable window is only 32 bits wide, we can put
|
| 338 | * 64-bit prefetchable resources in it.
|
| 339 |
|
| 340 | if ((res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) ==
|
| 341 | (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) {
|
| 342 | ret = pci_bus_alloc_resource(bus, res, size, align, min,
|
| 343 | IORESOURCE_PREFETCH,
|
| 344 | pcibios_align_resource, dev);
|
| 345 | if (ret == 0)
|
| 346 | return 0;
|
| 347 | }
|
| 348 |
|
| 349 | |
| 350 | * If we didn't find a better match, we can put any memory resource
|
| 351 | * in a non-prefetchable window. If this resource is 32 bits and
|
| 352 | * non-prefetchable, the first call already tried the only possibility
|
| 353 | * so we don't need to try again.
|
| 354 |
|
| 355 | if (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64))
|
| 356 | ret = pci_bus_alloc_resource(bus, res, size, align, min, 0,
|
| 357 | pcibios_align_resource, dev);
|
| 358 |
|
| 359 | return ret;
|
| 360 | }
|
| 361 |
|
| 362 | static int _pci_assign_resource(struct pci_dev *dev, int resno,
|
| 363 | resource_size_t size, resource_size_t min_align)
|
| 364 | {
|
| 365 | struct pci_bus *bus;
|
| 366 | int ret;
|
| 367 |
|
| 368 | bus = dev->bus;
|
| 369 | while ((ret = __pci_assign_resource(bus, dev, resno, size, min_align))) {
|
| 370 | if (!bus->parent || !bus->self->transparent)
|
| 371 | break;
|
| 372 | bus = bus->parent;
|
| 373 | }
|
| 374 |
|
| 375 | return ret;
|
| 376 | }
|
| 377 |
|
| 378 | int pci_assign_resource(struct pci_dev *dev, int resno)
|
| 379 | {
|
| 380 | struct resource *res = pci_resource_n(dev, resno);
|
| 381 | const char *res_name = pci_resource_name(dev, resno);
|
| 382 | resource_size_t align, size;
|
| 383 | int ret;
|
| 384 |
|
| 385 | if (res->flags & IORESOURCE_PCI_FIXED)
|
| 386 | return 0;
|
| 387 |
|
| 388 | res->flags |= IORESOURCE_UNSET;
|
| 389 | align = pci_resource_alignment(dev, res);
|
| 390 | if (!align) {
|
| 391 | pci_info(dev, "%s %pR: can't assign; bogus alignment\n",
|
| 392 | res_name, res);
|
| 393 | return -EINVAL;
|
| 394 | }
|
| 395 |
|
| 396 | size = resource_size(res);
|
| 397 | ret = _pci_assign_resource(dev, resno, size, align);
|
| 398 |
|
| 399 | |
| 400 | * If we failed to assign anything, let's try the address
|
| 401 | * where firmware left it. That at least has a chance of
|
| 402 | * working, which is better than just leaving it disabled.
|
| 403 |
|
| 404 | if (ret < 0) {
|
| 405 | pci_info(dev, "%s %pR: can't assign; no space\n", res_name, res);
|
| 406 | ret = pci_revert_fw_address(res, dev, resno, size);
|
| 407 | }
|
| 408 |
|
| 409 | if (ret < 0) {
|
| 410 | pci_info(dev, "%s %pR: failed to assign\n", res_name, res);
|
| 411 | return ret;
|
| 412 | }
|
| 413 |
|
| 414 | res->flags &= ~IORESOURCE_UNSET;
|
| 415 | res->flags &= ~IORESOURCE_STARTALIGN;
|
| 416 | if (pci_resource_is_bridge_win(resno))
|
| 417 | res->flags &= ~IORESOURCE_DISABLED;
|
| 418 |
|
| 419 | pci_info(dev, "%s %pR: assigned\n", res_name, res);
|
| 420 | if (resno < PCI_BRIDGE_RESOURCES)
|
| 421 | pci_update_resource(dev, resno);
|
| 422 |
|
| 423 | return 0;
|
| 424 | }
|
| 425 | EXPORT_SYMBOL(pci_assign_resource);
|
| 426 |
|
| 427 | int pci_reassign_resource(struct pci_dev *dev, int resno,
|
| 428 | resource_size_t addsize, resource_size_t min_align)
|
| 429 | {
|
| 430 | struct resource *res = pci_resource_n(dev, resno);
|
| 431 | const char *res_name = pci_resource_name(dev, resno);
|
| 432 | unsigned long flags;
|
| 433 | resource_size_t new_size;
|
| 434 | int ret;
|
| 435 |
|
| 436 | if (res->flags & IORESOURCE_PCI_FIXED)
|
| 437 | return 0;
|
| 438 |
|
| 439 | flags = res->flags;
|
| 440 | res->flags |= IORESOURCE_UNSET;
|
| 441 | if (!res->parent) {
|
| 442 | pci_info(dev, "%s %pR: can't reassign; unassigned resource\n",
|
| 443 | res_name, res);
|
| 444 | return -EINVAL;
|
| 445 | }
|
| 446 |
|
| 447 | new_size = resource_size(res) + addsize;
|
| 448 | ret = _pci_assign_resource(dev, resno, new_size, min_align);
|
| 449 | if (ret) {
|
| 450 | res->flags = flags;
|
| 451 | pci_info(dev, "%s %pR: failed to expand by %#llx\n",
|
| 452 | res_name, res, (unsigned long long) addsize);
|
| 453 | return ret;
|
| 454 | }
|
| 455 |
|
| 456 | res->flags &= ~IORESOURCE_UNSET;
|
| 457 | res->flags &= ~IORESOURCE_STARTALIGN;
|
| 458 | pci_info(dev, "%s %pR: reassigned; expanded by %#llx\n",
|
| 459 | res_name, res, (unsigned long long) addsize);
|
| 460 | if (resno < PCI_BRIDGE_RESOURCES)
|
| 461 | pci_update_resource(dev, resno);
|
| 462 |
|
| 463 | return 0;
|
| 464 | }
|
| 465 |
|
| 466 | int pci_release_resource(struct pci_dev *dev, int resno)
|
| 467 | {
|
| 468 | struct resource *res = pci_resource_n(dev, resno);
|
| 469 | const char *res_name = pci_resource_name(dev, resno);
|
| 470 | int ret;
|
| 471 |
|
| 472 | if (!res->parent)
|
| 473 | return 0;
|
| 474 |
|
| 475 | pci_info(dev, "%s %pR: releasing\n", res_name, res);
|
| 476 |
|
| 477 | ret = release_resource(res);
|
| 478 | if (ret)
|
| 479 | return ret;
|
| 480 | res->end = resource_size(res) - 1;
|
| 481 | res->start = 0;
|
| 482 | res->flags |= IORESOURCE_UNSET;
|
| 483 |
|
| 484 | return 0;
|
| 485 | }
|
| 486 | EXPORT_SYMBOL(pci_release_resource);
|
| 487 |
|
| 488 | int pci_enable_resources(struct pci_dev *dev, int mask)
|
| 489 | {
|
| 490 | u16 cmd, old_cmd;
|
| 491 | int i;
|
| 492 | struct resource *r;
|
| 493 | const char *r_name;
|
| 494 |
|
| 495 | pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
| 496 | old_cmd = cmd;
|
| 497 |
|
| 498 | pci_dev_for_each_resource(dev, r, i) {
|
| 499 | if (!(mask & (1 << i)))
|
| 500 | continue;
|
| 501 |
|
| 502 | r_name = pci_resource_name(dev, i);
|
| 503 |
|
| 504 | if (!(r->flags & (IORESOURCE_IO | IORESOURCE_MEM)))
|
| 505 | continue;
|
| 506 | if (pci_resource_is_optional(dev, i))
|
| 507 | continue;
|
| 508 |
|
| 509 | if (i < PCI_BRIDGE_RESOURCES) {
|
| 510 | if (r->flags & IORESOURCE_UNSET) {
|
| 511 | pci_err(dev, "%s %pR: not assigned; can't enable device\n",
|
| 512 | r_name, r);
|
| 513 | return -EINVAL;
|
| 514 | }
|
| 515 |
|
| 516 | if (!r->parent) {
|
| 517 | pci_err(dev, "%s %pR: not claimed; can't enable device\n",
|
| 518 | r_name, r);
|
| 519 | return -EINVAL;
|
| 520 | }
|
| 521 | }
|
| 522 |
|
| 523 | if (r->parent) {
|
| 524 | if (r->flags & IORESOURCE_IO)
|
| 525 | cmd |= PCI_COMMAND_IO;
|
| 526 | if (r->flags & IORESOURCE_MEM)
|
| 527 | cmd |= PCI_COMMAND_MEMORY;
|
| 528 | }
|
| 529 | }
|
| 530 |
|
| 531 | if (cmd != old_cmd) {
|
| 532 | pci_info(dev, "enabling device (%04x -> %04x)\n", old_cmd, cmd);
|
| 533 | pci_write_config_word(dev, PCI_COMMAND, cmd);
|
| 534 | }
|
| 535 | return 0;
|
| 536 | }
|