diff options
| author | Takashi Iwai <tiwai@suse.de> | 2009-09-10 09:33:04 -0400 |
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2009-09-10 09:33:04 -0400 |
| commit | 3827119e207823ff0f3e85271bef7a0dc953ee38 (patch) | |
| tree | 66d2a24524628b3123b39e1364281886d2f9074f | |
| parent | 9d416811f8cab11bf595b2880c557c33e3ae1ae9 (diff) | |
| parent | 93fe4483e6fd3e71d17cd919de14b3b1f9eb3795 (diff) | |
Merge branch 'topic/soundcore-preclaim' into for-linus
* topic/soundcore-preclaim:
sound: make OSS device number claiming optional and schedule its removal
sound: request char-major-* module aliases for missing OSS devices
chrdev: implement __[un]register_chrdev()
| -rw-r--r-- | Documentation/feature-removal-schedule.txt | 24 | ||||
| -rw-r--r-- | fs/char_dev.c | 39 | ||||
| -rw-r--r-- | include/linux/fs.h | 19 | ||||
| -rw-r--r-- | sound/Kconfig | 28 | ||||
| -rw-r--r-- | sound/sound_core.c | 100 |
5 files changed, 176 insertions, 34 deletions
diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 09e031c55887..f0690bbbd73c 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt | |||
| @@ -468,3 +468,27 @@ Why: cpu_policy_rwsem has a new cleaner definition making it local to | |||
| 468 | cpufreq core and contained inside cpufreq.c. Other dependent | 468 | cpufreq core and contained inside cpufreq.c. Other dependent |
| 469 | drivers should not use it in order to safely avoid lockdep issues. | 469 | drivers should not use it in order to safely avoid lockdep issues. |
| 470 | Who: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com> | 470 | Who: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com> |
| 471 | |||
| 472 | ---------------------------- | ||
| 473 | |||
| 474 | What: sound-slot/service-* module aliases and related clutters in | ||
| 475 | sound/sound_core.c | ||
| 476 | When: August 2010 | ||
| 477 | Why: OSS sound_core grabs all legacy minors (0-255) of SOUND_MAJOR | ||
| 478 | (14) and requests modules using custom sound-slot/service-* | ||
| 479 | module aliases. The only benefit of doing this is allowing | ||
| 480 | use of custom module aliases which might as well be considered | ||
| 481 | a bug at this point. This preemptive claiming prevents | ||
| 482 | alternative OSS implementations. | ||
| 483 | |||
| 484 | Till the feature is removed, the kernel will be requesting | ||
| 485 | both sound-slot/service-* and the standard char-major-* module | ||
| 486 | aliases and allow turning off the pre-claiming selectively via | ||
| 487 | CONFIG_SOUND_OSS_CORE_PRECLAIM and soundcore.preclaim_oss | ||
| 488 | kernel parameter. | ||
| 489 | |||
| 490 | After the transition phase is complete, both the custom module | ||
| 491 | aliases and switches to disable it will go away. This removal | ||
| 492 | will also allow making ALSA OSS emulation independent of | ||
| 493 | sound_core. The dependency will be broken then too. | ||
| 494 | Who: Tejun Heo <tj@kernel.org> | ||
diff --git a/fs/char_dev.c b/fs/char_dev.c index a173551e19d7..2f18c1e4e301 100644 --- a/fs/char_dev.c +++ b/fs/char_dev.c | |||
| @@ -237,8 +237,10 @@ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, | |||
| 237 | } | 237 | } |
| 238 | 238 | ||
| 239 | /** | 239 | /** |
| 240 | * register_chrdev() - Register a major number for character devices. | 240 | * __register_chrdev() - create and register a cdev occupying a range of minors |
| 241 | * @major: major device number or 0 for dynamic allocation | 241 | * @major: major device number or 0 for dynamic allocation |
| 242 | * @baseminor: first of the requested range of minor numbers | ||
| 243 | * @count: the number of minor numbers required | ||
| 242 | * @name: name of this range of devices | 244 | * @name: name of this range of devices |
| 243 | * @fops: file operations associated with this devices | 245 | * @fops: file operations associated with this devices |
| 244 | * | 246 | * |
| @@ -254,19 +256,17 @@ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, | |||
| 254 | * /dev. It only helps to keep track of the different owners of devices. If | 256 | * /dev. It only helps to keep track of the different owners of devices. If |
| 255 | * your module name has only one type of devices it's ok to use e.g. the name | 257 | * your module name has only one type of devices it's ok to use e.g. the name |
| 256 | * of the module here. | 258 | * of the module here. |
| 257 | * | ||
| 258 | * This function registers a range of 256 minor numbers. The first minor number | ||
| 259 | * is 0. | ||
| 260 | */ | 259 | */ |
| 261 | int register_chrdev(unsigned int major, const char *name, | 260 | int __register_chrdev(unsigned int major, unsigned int baseminor, |
| 262 | const struct file_operations *fops) | 261 | unsigned int count, const char *name, |
| 262 | const struct file_operations *fops) | ||
| 263 | { | 263 | { |
| 264 | struct char_device_struct *cd; | 264 | struct char_device_struct *cd; |
| 265 | struct cdev *cdev; | 265 | struct cdev *cdev; |
| 266 | char *s; | 266 | char *s; |
| 267 | int err = -ENOMEM; | 267 | int err = -ENOMEM; |
| 268 | 268 | ||
| 269 | cd = __register_chrdev_region(major, 0, 256, name); | 269 | cd = __register_chrdev_region(major, baseminor, count, name); |
| 270 | if (IS_ERR(cd)) | 270 | if (IS_ERR(cd)) |
| 271 | return PTR_ERR(cd); | 271 | return PTR_ERR(cd); |
| 272 | 272 | ||
| @@ -280,7 +280,7 @@ int register_chrdev(unsigned int major, const char *name, | |||
| 280 | for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/')) | 280 | for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/')) |
| 281 | *s = '!'; | 281 | *s = '!'; |
| 282 | 282 | ||
| 283 | err = cdev_add(cdev, MKDEV(cd->major, 0), 256); | 283 | err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); |
| 284 | if (err) | 284 | if (err) |
| 285 | goto out; | 285 | goto out; |
| 286 | 286 | ||
| @@ -290,7 +290,7 @@ int register_chrdev(unsigned int major, const char *name, | |||
| 290 | out: | 290 | out: |
| 291 | kobject_put(&cdev->kobj); | 291 | kobject_put(&cdev->kobj); |
| 292 | out2: | 292 | out2: |
| 293 | kfree(__unregister_chrdev_region(cd->major, 0, 256)); | 293 | kfree(__unregister_chrdev_region(cd->major, baseminor, count)); |
| 294 | return err; | 294 | return err; |
| 295 | } | 295 | } |
| 296 | 296 | ||
| @@ -316,10 +316,23 @@ void unregister_chrdev_region(dev_t from, unsigned count) | |||
| 316 | } | 316 | } |
| 317 | } | 317 | } |
| 318 | 318 | ||
| 319 | void unregister_chrdev(unsigned int major, const char *name) | 319 | /** |
| 320 | * __unregister_chrdev - unregister and destroy a cdev | ||
| 321 | * @major: major device number | ||
| 322 | * @baseminor: first of the range of minor numbers | ||
| 323 | * @count: the number of minor numbers this cdev is occupying | ||
| 324 | * @name: name of this range of devices | ||
| 325 | * | ||
| 326 | * Unregister and destroy the cdev occupying the region described by | ||
| 327 | * @major, @baseminor and @count. This function undoes what | ||
| 328 | * __register_chrdev() did. | ||
| 329 | */ | ||
| 330 | void __unregister_chrdev(unsigned int major, unsigned int baseminor, | ||
| 331 | unsigned int count, const char *name) | ||
| 320 | { | 332 | { |
| 321 | struct char_device_struct *cd; | 333 | struct char_device_struct *cd; |
| 322 | cd = __unregister_chrdev_region(major, 0, 256); | 334 | |
| 335 | cd = __unregister_chrdev_region(major, baseminor, count); | ||
| 323 | if (cd && cd->cdev) | 336 | if (cd && cd->cdev) |
| 324 | cdev_del(cd->cdev); | 337 | cdev_del(cd->cdev); |
| 325 | kfree(cd); | 338 | kfree(cd); |
| @@ -568,6 +581,6 @@ EXPORT_SYMBOL(cdev_alloc); | |||
| 568 | EXPORT_SYMBOL(cdev_del); | 581 | EXPORT_SYMBOL(cdev_del); |
| 569 | EXPORT_SYMBOL(cdev_add); | 582 | EXPORT_SYMBOL(cdev_add); |
| 570 | EXPORT_SYMBOL(cdev_index); | 583 | EXPORT_SYMBOL(cdev_index); |
| 571 | EXPORT_SYMBOL(register_chrdev); | 584 | EXPORT_SYMBOL(__register_chrdev); |
| 572 | EXPORT_SYMBOL(unregister_chrdev); | 585 | EXPORT_SYMBOL(__unregister_chrdev); |
| 573 | EXPORT_SYMBOL(directly_mappable_cdev_bdi); | 586 | EXPORT_SYMBOL(directly_mappable_cdev_bdi); |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 73e9b643e455..3972ffb597c5 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
| @@ -1998,12 +1998,25 @@ extern void bd_release_from_disk(struct block_device *, struct gendisk *); | |||
| 1998 | #define CHRDEV_MAJOR_HASH_SIZE 255 | 1998 | #define CHRDEV_MAJOR_HASH_SIZE 255 |
| 1999 | extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); | 1999 | extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *); |
| 2000 | extern int register_chrdev_region(dev_t, unsigned, const char *); | 2000 | extern int register_chrdev_region(dev_t, unsigned, const char *); |
| 2001 | extern int register_chrdev(unsigned int, const char *, | 2001 | extern int __register_chrdev(unsigned int major, unsigned int baseminor, |
| 2002 | const struct file_operations *); | 2002 | unsigned int count, const char *name, |
| 2003 | extern void unregister_chrdev(unsigned int, const char *); | 2003 | const struct file_operations *fops); |
| 2004 | extern void __unregister_chrdev(unsigned int major, unsigned int baseminor, | ||
| 2005 | unsigned int count, const char *name); | ||
| 2004 | extern void unregister_chrdev_region(dev_t, unsigned); | 2006 | extern void unregister_chrdev_region(dev_t, unsigned); |
| 2005 | extern void chrdev_show(struct seq_file *,off_t); | 2007 | extern void chrdev_show(struct seq_file *,off_t); |
| 2006 | 2008 | ||
| 2009 | static inline int register_chrdev(unsigned int major, const char *name, | ||
| 2010 | const struct file_operations *fops) | ||
| 2011 | { | ||
| 2012 | return __register_chrdev(major, 0, 256, name, fops); | ||
| 2013 | } | ||
| 2014 | |||
| 2015 | static inline void unregister_chrdev(unsigned int major, const char *name) | ||
| 2016 | { | ||
| 2017 | __unregister_chrdev(major, 0, 256, name); | ||
| 2018 | } | ||
| 2019 | |||
| 2007 | /* fs/block_dev.c */ | 2020 | /* fs/block_dev.c */ |
| 2008 | #define BDEVNAME_SIZE 32 /* Largest string for a blockdev identifier */ | 2021 | #define BDEVNAME_SIZE 32 /* Largest string for a blockdev identifier */ |
| 2009 | #define BDEVT_SIZE 10 /* Largest string for MAJ:MIN for blkdev */ | 2022 | #define BDEVT_SIZE 10 /* Largest string for MAJ:MIN for blkdev */ |
diff --git a/sound/Kconfig b/sound/Kconfig index 1eceb85287c5..439e15c8faa3 100644 --- a/sound/Kconfig +++ b/sound/Kconfig | |||
| @@ -32,6 +32,34 @@ config SOUND_OSS_CORE | |||
| 32 | bool | 32 | bool |
| 33 | default n | 33 | default n |
| 34 | 34 | ||
| 35 | config SOUND_OSS_CORE_PRECLAIM | ||
| 36 | bool "Preclaim OSS device numbers" | ||
| 37 | depends on SOUND_OSS_CORE | ||
| 38 | default y | ||
| 39 | help | ||
| 40 | With this option enabled, the kernel will claim all OSS device | ||
| 41 | numbers if any OSS support (native or emulation) is enabled | ||
| 42 | whether the respective module is loaded or not and try to load the | ||
| 43 | appropriate module using sound-slot/service-* and char-major-* | ||
| 44 | module aliases when one of the device numbers is opened. With | ||
| 45 | this option disabled, kernel will only claim actually in-use | ||
| 46 | device numbers and opening a missing device will generate only the | ||
| 47 | standard char-major-* aliases. | ||
| 48 | |||
| 49 | The only visible difference is use of additional module aliases | ||
| 50 | and whether OSS sound devices appear multiple times in | ||
| 51 | /proc/devices. sound-slot/service-* module aliases are scheduled | ||
| 52 | to be removed (ie. PRECLAIM won't be available) and this option is | ||
| 53 | to make the transition easier. This option can be overridden | ||
| 54 | during boot using the kernel parameter soundcore.preclaim_oss. | ||
| 55 | |||
| 56 | Disabling this allows alternative OSS implementations. | ||
| 57 | |||
| 58 | Please read Documentation/feature-removal-schedule.txt for | ||
| 59 | details. | ||
| 60 | |||
| 61 | If unusre, say Y. | ||
| 62 | |||
| 35 | source "sound/oss/dmasound/Kconfig" | 63 | source "sound/oss/dmasound/Kconfig" |
| 36 | 64 | ||
| 37 | if !M68K | 65 | if !M68K |
diff --git a/sound/sound_core.c b/sound/sound_core.c index a41f8b127f49..bb4b88e606bb 100644 --- a/sound/sound_core.c +++ b/sound/sound_core.c | |||
| @@ -128,6 +128,46 @@ extern int msnd_pinnacle_init(void); | |||
| 128 | #endif | 128 | #endif |
| 129 | 129 | ||
| 130 | /* | 130 | /* |
| 131 | * By default, OSS sound_core claims full legacy minor range (0-255) | ||
| 132 | * of SOUND_MAJOR to trap open attempts to any sound minor and | ||
| 133 | * requests modules using custom sound-slot/service-* module aliases. | ||
| 134 | * The only benefit of doing this is allowing use of custom module | ||
| 135 | * aliases instead of the standard char-major-* ones. This behavior | ||
| 136 | * prevents alternative OSS implementation and is scheduled to be | ||
| 137 | * removed. | ||
| 138 | * | ||
| 139 | * CONFIG_SOUND_OSS_CORE_PRECLAIM and soundcore.preclaim_oss kernel | ||
| 140 | * parameter are added to allow distros and developers to try and | ||
| 141 | * switch to alternative implementations without needing to rebuild | ||
| 142 | * the kernel in the meantime. If preclaim_oss is non-zero, the | ||
| 143 | * kernel will behave the same as before. All SOUND_MAJOR minors are | ||
| 144 | * preclaimed and the custom module aliases along with standard chrdev | ||
| 145 | * ones are emitted if a missing device is opened. If preclaim_oss is | ||
| 146 | * zero, sound_core only grabs what's actually in use and for missing | ||
| 147 | * devices only the standard chrdev aliases are requested. | ||
| 148 | * | ||
| 149 | * All these clutters are scheduled to be removed along with | ||
| 150 | * sound-slot/service-* module aliases. Please take a look at | ||
| 151 | * feature-removal-schedule.txt for details. | ||
| 152 | */ | ||
| 153 | #ifdef CONFIG_SOUND_OSS_CORE_PRECLAIM | ||
| 154 | static int preclaim_oss = 1; | ||
| 155 | #else | ||
| 156 | static int preclaim_oss = 0; | ||
| 157 | #endif | ||
| 158 | |||
| 159 | module_param(preclaim_oss, int, 0444); | ||
| 160 | |||
| 161 | static int soundcore_open(struct inode *, struct file *); | ||
| 162 | |||
| 163 | static const struct file_operations soundcore_fops = | ||
| 164 | { | ||
| 165 | /* We must have an owner or the module locking fails */ | ||
| 166 | .owner = THIS_MODULE, | ||
| 167 | .open = soundcore_open, | ||
| 168 | }; | ||
| 169 | |||
| 170 | /* | ||
| 131 | * Low level list operator. Scan the ordered list, find a hole and | 171 | * Low level list operator. Scan the ordered list, find a hole and |
| 132 | * join into it. Called with the lock asserted | 172 | * join into it. Called with the lock asserted |
| 133 | */ | 173 | */ |
| @@ -219,8 +259,9 @@ static int sound_insert_unit(struct sound_unit **list, const struct file_operati | |||
| 219 | 259 | ||
| 220 | if (!s) | 260 | if (!s) |
| 221 | return -ENOMEM; | 261 | return -ENOMEM; |
| 222 | 262 | ||
| 223 | spin_lock(&sound_loader_lock); | 263 | spin_lock(&sound_loader_lock); |
| 264 | retry: | ||
| 224 | r = __sound_insert_unit(s, list, fops, index, low, top); | 265 | r = __sound_insert_unit(s, list, fops, index, low, top); |
| 225 | spin_unlock(&sound_loader_lock); | 266 | spin_unlock(&sound_loader_lock); |
| 226 | 267 | ||
| @@ -231,11 +272,31 @@ static int sound_insert_unit(struct sound_unit **list, const struct file_operati | |||
| 231 | else | 272 | else |
| 232 | sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP); | 273 | sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP); |
| 233 | 274 | ||
| 275 | if (!preclaim_oss) { | ||
| 276 | /* | ||
| 277 | * Something else might have grabbed the minor. If | ||
| 278 | * first free slot is requested, rescan with @low set | ||
| 279 | * to the next unit; otherwise, -EBUSY. | ||
| 280 | */ | ||
| 281 | r = __register_chrdev(SOUND_MAJOR, s->unit_minor, 1, s->name, | ||
| 282 | &soundcore_fops); | ||
| 283 | if (r < 0) { | ||
| 284 | spin_lock(&sound_loader_lock); | ||
| 285 | __sound_remove_unit(list, s->unit_minor); | ||
| 286 | if (index < 0) { | ||
| 287 | low = s->unit_minor + SOUND_STEP; | ||
| 288 | goto retry; | ||
| 289 | } | ||
| 290 | spin_unlock(&sound_loader_lock); | ||
| 291 | return -EBUSY; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | |||
| 234 | device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), | 295 | device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), |
| 235 | NULL, s->name+6); | 296 | NULL, s->name+6); |
| 236 | return r; | 297 | return s->unit_minor; |
| 237 | 298 | ||
| 238 | fail: | 299 | fail: |
| 239 | kfree(s); | 300 | kfree(s); |
| 240 | return r; | 301 | return r; |
| 241 | } | 302 | } |
| @@ -254,6 +315,9 @@ static void sound_remove_unit(struct sound_unit **list, int unit) | |||
| 254 | p = __sound_remove_unit(list, unit); | 315 | p = __sound_remove_unit(list, unit); |
| 255 | spin_unlock(&sound_loader_lock); | 316 | spin_unlock(&sound_loader_lock); |
| 256 | if (p) { | 317 | if (p) { |
| 318 | if (!preclaim_oss) | ||
| 319 | __unregister_chrdev(SOUND_MAJOR, p->unit_minor, 1, | ||
| 320 | p->name); | ||
| 257 | device_destroy(sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); | 321 | device_destroy(sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); |
| 258 | kfree(p); | 322 | kfree(p); |
| 259 | } | 323 | } |
| @@ -491,19 +555,6 @@ void unregister_sound_dsp(int unit) | |||
| 491 | 555 | ||
| 492 | EXPORT_SYMBOL(unregister_sound_dsp); | 556 | EXPORT_SYMBOL(unregister_sound_dsp); |
| 493 | 557 | ||
| 494 | /* | ||
| 495 | * Now our file operations | ||
| 496 | */ | ||
| 497 | |||
| 498 | static int soundcore_open(struct inode *, struct file *); | ||
| 499 | |||
| 500 | static const struct file_operations soundcore_fops= | ||
| 501 | { | ||
| 502 | /* We must have an owner or the module locking fails */ | ||
| 503 | .owner = THIS_MODULE, | ||
| 504 | .open = soundcore_open, | ||
| 505 | }; | ||
| 506 | |||
| 507 | static struct sound_unit *__look_for_unit(int chain, int unit) | 558 | static struct sound_unit *__look_for_unit(int chain, int unit) |
| 508 | { | 559 | { |
| 509 | struct sound_unit *s; | 560 | struct sound_unit *s; |
| @@ -539,8 +590,9 @@ static int soundcore_open(struct inode *inode, struct file *file) | |||
| 539 | s = __look_for_unit(chain, unit); | 590 | s = __look_for_unit(chain, unit); |
| 540 | if (s) | 591 | if (s) |
| 541 | new_fops = fops_get(s->unit_fops); | 592 | new_fops = fops_get(s->unit_fops); |
| 542 | if (!new_fops) { | 593 | if (preclaim_oss && !new_fops) { |
| 543 | spin_unlock(&sound_loader_lock); | 594 | spin_unlock(&sound_loader_lock); |
| 595 | |||
| 544 | /* | 596 | /* |
| 545 | * Please, don't change this order or code. | 597 | * Please, don't change this order or code. |
| 546 | * For ALSA slot means soundcard and OSS emulation code | 598 | * For ALSA slot means soundcard and OSS emulation code |
| @@ -550,6 +602,17 @@ static int soundcore_open(struct inode *inode, struct file *file) | |||
| 550 | */ | 602 | */ |
| 551 | request_module("sound-slot-%i", unit>>4); | 603 | request_module("sound-slot-%i", unit>>4); |
| 552 | request_module("sound-service-%i-%i", unit>>4, chain); | 604 | request_module("sound-service-%i-%i", unit>>4, chain); |
| 605 | |||
| 606 | /* | ||
| 607 | * sound-slot/service-* module aliases are scheduled | ||
| 608 | * for removal in favor of the standard char-major-* | ||
| 609 | * module aliases. For the time being, generate both | ||
| 610 | * the legacy and standard module aliases to ease | ||
| 611 | * transition. | ||
| 612 | */ | ||
| 613 | if (request_module("char-major-%d-%d", SOUND_MAJOR, unit) > 0) | ||
| 614 | request_module("char-major-%d", SOUND_MAJOR); | ||
| 615 | |||
| 553 | spin_lock(&sound_loader_lock); | 616 | spin_lock(&sound_loader_lock); |
| 554 | s = __look_for_unit(chain, unit); | 617 | s = __look_for_unit(chain, unit); |
| 555 | if (s) | 618 | if (s) |
| @@ -593,7 +656,8 @@ static void cleanup_oss_soundcore(void) | |||
| 593 | 656 | ||
| 594 | static int __init init_oss_soundcore(void) | 657 | static int __init init_oss_soundcore(void) |
| 595 | { | 658 | { |
| 596 | if (register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)==-1) { | 659 | if (preclaim_oss && |
| 660 | register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops) == -1) { | ||
| 597 | printk(KERN_ERR "soundcore: sound device already in use.\n"); | 661 | printk(KERN_ERR "soundcore: sound device already in use.\n"); |
| 598 | return -EBUSY; | 662 | return -EBUSY; |
| 599 | } | 663 | } |
