1 use super::{_port, hba::HbaCmdTable}; 2 use crate::arch::MMArch; 3 use crate::driver::base::block::block_device::{ 4 BlockDevName, BlockDevice, BlockId, GeneralBlockRange, 5 }; 6 use crate::driver::base::block::disk_info::Partition; 7 use crate::driver::base::block::manager::BlockDevMeta; 8 use crate::driver::base::class::Class; 9 use crate::driver::base::device::bus::Bus; 10 11 use crate::driver::base::device::driver::Driver; 12 use crate::driver::base::device::{Device, DeviceType, IdTable}; 13 use crate::driver::base::kobject::{KObjType, KObject, KObjectState}; 14 use crate::driver::base::kset::KSet; 15 use crate::driver::disk::ahci::HBA_PxIS_TFES; 16 17 use crate::driver::scsi::scsi_manager; 18 use crate::filesystem::kernfs::KernFSInode; 19 use crate::filesystem::mbr::MbrDiskPartionTable; 20 21 use crate::driver::disk::ahci::hba::{ 22 FisRegH2D, FisType, HbaCmdHeader, ATA_CMD_READ_DMA_EXT, ATA_CMD_WRITE_DMA_EXT, ATA_DEV_BUSY, 23 ATA_DEV_DRQ, 24 }; 25 use crate::libs::rwlock::{RwLockReadGuard, RwLockWriteGuard}; 26 use crate::libs::spinlock::{SpinLock, SpinLockGuard}; 27 use crate::mm::{verify_area, MemoryManagementArch, PhysAddr, VirtAddr}; 28 use log::error; 29 use system_error::SystemError; 30 31 use alloc::sync::Weak; 32 use alloc::{sync::Arc, vec::Vec}; 33 34 use core::fmt::Debug; 35 use core::sync::atomic::{compiler_fence, Ordering}; 36 use core::{mem::size_of, ptr::write_bytes}; 37 38 /// @brief: 只支持MBR分区格式的磁盘结构体 39 pub struct AhciDisk { 40 // 磁盘的状态flags 41 pub partitions: Vec<Arc<Partition>>, // 磁盘分区数组 42 // port: &'static mut HbaPort, // 控制硬盘的端口 43 pub ctrl_num: u8, 44 pub port_num: u8, 45 /// 指向LockAhciDisk的弱引用 46 self_ref: Weak<LockedAhciDisk>, 47 } 48 49 /// @brief: 带锁的AhciDisk 50 #[derive(Debug)] 51 pub struct LockedAhciDisk { 52 blkdev_meta: BlockDevMeta, 53 inner: SpinLock<AhciDisk>, 54 } 55 56 impl LockedAhciDisk { inner(&self) -> SpinLockGuard<AhciDisk>57 pub fn inner(&self) -> SpinLockGuard<AhciDisk> { 58 self.inner.lock() 59 } 60 } 61 62 /// 函数实现 63 impl Debug for AhciDisk { fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 65 write!(f, "AhciDisk") 66 } 67 } 68 69 impl AhciDisk { read_at( &self, lba_id_start: BlockId, count: usize, buf: &mut [u8], ) -> Result<usize, SystemError>70 fn read_at( 71 &self, 72 lba_id_start: BlockId, // 起始lba编号 73 count: usize, // 读取lba的数量 74 buf: &mut [u8], 75 ) -> Result<usize, SystemError> { 76 assert!((buf.len() & 511) == 0); 77 compiler_fence(Ordering::SeqCst); 78 let check_length = ((count - 1) >> 4) + 1; // prdt length 79 if count * 512 > buf.len() || check_length > 8_usize { 80 error!("ahci read: e2big"); 81 // 不可能的操作 82 return Err(SystemError::E2BIG); 83 } else if count == 0 { 84 return Ok(0); 85 } 86 87 let port = _port(self.ctrl_num, self.port_num); 88 volatile_write!(port.is, u32::MAX); // Clear pending interrupt bits 89 90 let slot = port.find_cmdslot().unwrap_or(u32::MAX); 91 92 if slot == u32::MAX { 93 return Err(SystemError::EIO); 94 } 95 96 #[allow(unused_unsafe)] 97 let cmdheader: &mut HbaCmdHeader = unsafe { 98 (MMArch::phys_2_virt(PhysAddr::new( 99 volatile_read!(port.clb) as usize + slot as usize * size_of::<HbaCmdHeader>(), 100 )) 101 .unwrap() 102 .data() as *mut HbaCmdHeader) 103 .as_mut() 104 .unwrap() 105 }; 106 107 cmdheader.cfl = (size_of::<FisRegH2D>() / size_of::<u32>()) as u8; 108 109 volatile_set_bit!(cmdheader.cfl, 1 << 6, false); // Read/Write bit : Read from device 110 volatile_write!(cmdheader.prdtl, check_length as u16); // PRDT entries count 111 112 // 设置数据存放地址 113 let mut buf_ptr = buf as *mut [u8] as *mut usize as usize; 114 115 // 由于目前的内存管理机制无法把用户空间的内存地址转换为物理地址,所以只能先把数据拷贝到内核空间 116 // TODO:在内存管理重构后,可以直接使用用户空间的内存地址 117 118 let user_buf = verify_area(VirtAddr::new(buf_ptr), buf.len()).is_ok(); 119 let mut kbuf = if user_buf { 120 let x: Vec<u8> = vec![0; buf.len()]; 121 Some(x) 122 } else { 123 None 124 }; 125 126 if kbuf.is_some() { 127 buf_ptr = kbuf.as_mut().unwrap().as_mut_ptr() as usize; 128 } 129 130 #[allow(unused_unsafe)] 131 let cmdtbl = unsafe { 132 (MMArch::phys_2_virt(PhysAddr::new(volatile_read!(cmdheader.ctba) as usize)) 133 .unwrap() 134 .data() as *mut HbaCmdTable) 135 .as_mut() 136 .unwrap() // 必须使用 as_mut ,得到的才是原来的变量 137 }; 138 let mut tmp_count = count; 139 140 unsafe { 141 // 清空整个table的旧数据 142 write_bytes(cmdtbl, 0, 1); 143 } 144 // debug!("cmdheader.prdtl={}", volatile_read!(cmdheader.prdtl)); 145 146 // 8K bytes (16 sectors) per PRDT 147 for i in 0..((volatile_read!(cmdheader.prdtl) - 1) as usize) { 148 volatile_write!( 149 cmdtbl.prdt_entry[i].dba, 150 MMArch::virt_2_phys(VirtAddr::new(buf_ptr)).unwrap().data() as u64 151 ); 152 cmdtbl.prdt_entry[i].dbc = 8 * 1024 - 1; 153 volatile_set_bit!(cmdtbl.prdt_entry[i].dbc, 1 << 31, true); // 允许中断 prdt_entry.i 154 buf_ptr += 8 * 1024; 155 tmp_count -= 16; 156 } 157 158 // Last entry 159 let las = (volatile_read!(cmdheader.prdtl) - 1) as usize; 160 volatile_write!( 161 cmdtbl.prdt_entry[las].dba, 162 MMArch::virt_2_phys(VirtAddr::new(buf_ptr)).unwrap().data() as u64 163 ); 164 cmdtbl.prdt_entry[las].dbc = ((tmp_count << 9) - 1) as u32; // 数据长度 165 166 volatile_set_bit!(cmdtbl.prdt_entry[las].dbc, 1 << 31, true); // 允许中断 167 168 // 设置命令 169 let cmdfis = unsafe { 170 ((&mut cmdtbl.cfis) as *mut [u8] as *mut usize as *mut FisRegH2D) 171 .as_mut() 172 .unwrap() 173 }; 174 volatile_write!(cmdfis.fis_type, FisType::RegH2D as u8); 175 volatile_set_bit!(cmdfis.pm, 1 << 7, true); // command_bit set 176 volatile_write!(cmdfis.command, ATA_CMD_READ_DMA_EXT); 177 178 volatile_write!(cmdfis.lba0, (lba_id_start & 0xFF) as u8); 179 volatile_write!(cmdfis.lba1, ((lba_id_start >> 8) & 0xFF) as u8); 180 volatile_write!(cmdfis.lba2, ((lba_id_start >> 16) & 0xFF) as u8); 181 volatile_write!(cmdfis.lba3, ((lba_id_start >> 24) & 0xFF) as u8); 182 volatile_write!(cmdfis.lba4, ((lba_id_start >> 32) & 0xFF) as u8); 183 volatile_write!(cmdfis.lba5, ((lba_id_start >> 40) & 0xFF) as u8); 184 185 volatile_write!(cmdfis.countl, (count & 0xFF) as u8); 186 volatile_write!(cmdfis.counth, ((count >> 8) & 0xFF) as u8); 187 188 volatile_write!(cmdfis.device, 1 << 6); // LBA Mode 189 190 // 等待之前的操作完成 191 let mut spin_count = 0; 192 const SPIN_LIMIT: u32 = 10000; 193 194 while (volatile_read!(port.tfd) as u8 & (ATA_DEV_BUSY | ATA_DEV_DRQ)) > 0 195 && spin_count < SPIN_LIMIT 196 { 197 spin_count += 1; 198 } 199 200 if spin_count == SPIN_LIMIT { 201 error!("Port is hung"); 202 return Err(SystemError::EIO); 203 } 204 205 volatile_set_bit!(port.ci, 1 << slot, true); // Issue command 206 // debug!("To wait ahci read complete."); 207 // 等待操作完成 208 loop { 209 if (volatile_read!(port.ci) & (1 << slot)) == 0 { 210 break; 211 } 212 if (volatile_read!(port.is) & HBA_PxIS_TFES) > 0 { 213 error!("Read disk error"); 214 return Err(SystemError::EIO); 215 } 216 } 217 if let Some(kbuf) = &kbuf { 218 buf.copy_from_slice(kbuf); 219 } 220 221 compiler_fence(Ordering::SeqCst); 222 // successfully read 223 return Ok(count * 512); 224 } 225 write_at( &self, lba_id_start: BlockId, count: usize, buf: &[u8], ) -> Result<usize, SystemError>226 fn write_at( 227 &self, 228 lba_id_start: BlockId, 229 count: usize, 230 buf: &[u8], 231 ) -> Result<usize, SystemError> { 232 assert!((buf.len() & 511) == 0); 233 compiler_fence(Ordering::SeqCst); 234 let check_length = ((count - 1) >> 4) + 1; // prdt length 235 if count * 512 > buf.len() || check_length > 8 { 236 // 不可能的操作 237 return Err(SystemError::E2BIG); 238 } else if count == 0 { 239 return Ok(0); 240 } 241 242 let port = _port(self.ctrl_num, self.port_num); 243 244 volatile_write!(port.is, u32::MAX); // Clear pending interrupt bits 245 246 let slot = port.find_cmdslot().unwrap_or(u32::MAX); 247 248 if slot == u32::MAX { 249 return Err(SystemError::EIO); 250 } 251 252 compiler_fence(Ordering::SeqCst); 253 #[allow(unused_unsafe)] 254 let cmdheader: &mut HbaCmdHeader = unsafe { 255 (MMArch::phys_2_virt(PhysAddr::new( 256 volatile_read!(port.clb) as usize + slot as usize * size_of::<HbaCmdHeader>(), 257 )) 258 .unwrap() 259 .data() as *mut HbaCmdHeader) 260 .as_mut() 261 .unwrap() 262 }; 263 compiler_fence(Ordering::SeqCst); 264 265 volatile_write_bit!( 266 cmdheader.cfl, 267 (1 << 5) - 1_u8, 268 (size_of::<FisRegH2D>() / size_of::<u32>()) as u8 269 ); // Command FIS size 270 271 volatile_set_bit!(cmdheader.cfl, 7 << 5, true); // (p,c,w)都设置为1, Read/Write bit : Write from device 272 volatile_write!(cmdheader.prdtl, check_length as u16); // PRDT entries count 273 274 // 设置数据存放地址 275 compiler_fence(Ordering::SeqCst); 276 let mut buf_ptr = buf as *const [u8] as *mut usize as usize; 277 278 // 由于目前的内存管理机制无法把用户空间的内存地址转换为物理地址,所以只能先把数据拷贝到内核空间 279 // TODO:在内存管理重构后,可以直接使用用户空间的内存地址 280 let user_buf = verify_area(VirtAddr::new(buf_ptr), buf.len()).is_ok(); 281 let mut kbuf = if user_buf { 282 let mut x: Vec<u8> = vec![0; buf.len()]; 283 x.resize(buf.len(), 0); 284 x.copy_from_slice(buf); 285 Some(x) 286 } else { 287 None 288 }; 289 290 if kbuf.is_some() { 291 buf_ptr = kbuf.as_mut().unwrap().as_mut_ptr() as usize; 292 } 293 294 #[allow(unused_unsafe)] 295 let cmdtbl = unsafe { 296 (MMArch::phys_2_virt(PhysAddr::new(volatile_read!(cmdheader.ctba) as usize)) 297 .unwrap() 298 .data() as *mut HbaCmdTable) 299 .as_mut() 300 .unwrap() 301 }; 302 let mut tmp_count = count; 303 compiler_fence(Ordering::SeqCst); 304 305 unsafe { 306 // 清空整个table的旧数据 307 write_bytes(cmdtbl, 0, 1); 308 } 309 310 // 8K bytes (16 sectors) per PRDT 311 for i in 0..((volatile_read!(cmdheader.prdtl) - 1) as usize) { 312 volatile_write!( 313 cmdtbl.prdt_entry[i].dba, 314 MMArch::virt_2_phys(VirtAddr::new(buf_ptr)).unwrap().data() as u64 315 ); 316 volatile_write_bit!(cmdtbl.prdt_entry[i].dbc, (1 << 22) - 1, 8 * 1024 - 1); // 数据长度 317 volatile_set_bit!(cmdtbl.prdt_entry[i].dbc, 1 << 31, true); // 允许中断 318 buf_ptr += 8 * 1024; 319 tmp_count -= 16; 320 } 321 322 // Last entry 323 let las = (volatile_read!(cmdheader.prdtl) - 1) as usize; 324 volatile_write!( 325 cmdtbl.prdt_entry[las].dba, 326 MMArch::virt_2_phys(VirtAddr::new(buf_ptr)).unwrap().data() as u64 327 ); 328 volatile_set_bit!(cmdtbl.prdt_entry[las].dbc, 1 << 31, true); // 允许中断 329 volatile_write_bit!( 330 cmdtbl.prdt_entry[las].dbc, 331 (1 << 22) - 1, 332 ((tmp_count << 9) - 1) as u32 333 ); // 数据长度 334 335 // 设置命令 336 let cmdfis = unsafe { 337 ((&mut cmdtbl.cfis) as *mut [u8] as *mut usize as *mut FisRegH2D) 338 .as_mut() 339 .unwrap() 340 }; 341 volatile_write!(cmdfis.fis_type, FisType::RegH2D as u8); 342 volatile_set_bit!(cmdfis.pm, 1 << 7, true); // command_bit set 343 volatile_write!(cmdfis.command, ATA_CMD_WRITE_DMA_EXT); 344 345 volatile_write!(cmdfis.lba0, (lba_id_start & 0xFF) as u8); 346 volatile_write!(cmdfis.lba1, ((lba_id_start >> 8) & 0xFF) as u8); 347 volatile_write!(cmdfis.lba2, ((lba_id_start >> 16) & 0xFF) as u8); 348 volatile_write!(cmdfis.lba3, ((lba_id_start >> 24) & 0xFF) as u8); 349 volatile_write!(cmdfis.lba4, ((lba_id_start >> 32) & 0xFF) as u8); 350 volatile_write!(cmdfis.lba5, ((lba_id_start >> 40) & 0xFF) as u8); 351 352 volatile_write!(cmdfis.countl, (count & 0xFF) as u8); 353 volatile_write!(cmdfis.counth, ((count >> 8) & 0xFF) as u8); 354 355 volatile_write!(cmdfis.device, 1 << 6); // LBA Mode 356 357 volatile_set_bit!(port.ci, 1 << slot, true); // Issue command 358 359 // 等待操作完成 360 loop { 361 if (volatile_read!(port.ci) & (1 << slot)) == 0 { 362 break; 363 } 364 if (volatile_read!(port.is) & HBA_PxIS_TFES) > 0 { 365 error!("Write disk error"); 366 return Err(SystemError::EIO); 367 } 368 } 369 370 compiler_fence(Ordering::SeqCst); 371 // successfully read 372 return Ok(count * 512); 373 } 374 sync(&self) -> Result<(), SystemError>375 fn sync(&self) -> Result<(), SystemError> { 376 // 由于目前没有block cache, 因此sync返回成功即可 377 return Ok(()); 378 } 379 } 380 381 impl LockedAhciDisk { new(ctrl_num: u8, port_num: u8) -> Result<Arc<LockedAhciDisk>, SystemError>382 pub fn new(ctrl_num: u8, port_num: u8) -> Result<Arc<LockedAhciDisk>, SystemError> { 383 let devname = scsi_manager().alloc_id().ok_or(SystemError::EBUSY)?; 384 // 构建磁盘结构体 385 let result: Arc<LockedAhciDisk> = Arc::new_cyclic(|self_ref| LockedAhciDisk { 386 blkdev_meta: BlockDevMeta::new(devname), 387 inner: SpinLock::new(AhciDisk { 388 partitions: Vec::new(), 389 ctrl_num, 390 port_num, 391 self_ref: self_ref.clone(), 392 }), 393 }); 394 let table: MbrDiskPartionTable = result.read_mbr_table()?; 395 396 // 求出有多少可用分区 397 let partitions = table.partitions(Arc::downgrade(&result) as Weak<dyn BlockDevice>); 398 result.inner().partitions = partitions; 399 400 return Ok(result); 401 } 402 403 /// @brief: 从磁盘中读取 MBR 分区表结构体 read_mbr_table(&self) -> Result<MbrDiskPartionTable, SystemError>404 pub fn read_mbr_table(&self) -> Result<MbrDiskPartionTable, SystemError> { 405 let disk = self.inner().self_ref.upgrade().unwrap() as Arc<dyn BlockDevice>; 406 MbrDiskPartionTable::from_disk(disk) 407 } 408 } 409 410 impl KObject for LockedAhciDisk { as_any_ref(&self) -> &dyn core::any::Any411 fn as_any_ref(&self) -> &dyn core::any::Any { 412 self 413 } 414 inode(&self) -> Option<Arc<KernFSInode>>415 fn inode(&self) -> Option<Arc<KernFSInode>> { 416 todo!() 417 } 418 kobj_type(&self) -> Option<&'static dyn KObjType>419 fn kobj_type(&self) -> Option<&'static dyn KObjType> { 420 todo!() 421 } 422 kset(&self) -> Option<Arc<KSet>>423 fn kset(&self) -> Option<Arc<KSet>> { 424 todo!() 425 } 426 parent(&self) -> Option<Weak<dyn KObject>>427 fn parent(&self) -> Option<Weak<dyn KObject>> { 428 todo!() 429 } 430 set_inode(&self, _inode: Option<Arc<KernFSInode>>)431 fn set_inode(&self, _inode: Option<Arc<KernFSInode>>) { 432 todo!() 433 } 434 kobj_state(&self) -> RwLockReadGuard<KObjectState>435 fn kobj_state(&self) -> RwLockReadGuard<KObjectState> { 436 todo!() 437 } 438 kobj_state_mut(&self) -> RwLockWriteGuard<KObjectState>439 fn kobj_state_mut(&self) -> RwLockWriteGuard<KObjectState> { 440 todo!() 441 } 442 set_kobj_state(&self, _state: KObjectState)443 fn set_kobj_state(&self, _state: KObjectState) { 444 todo!() 445 } 446 name(&self) -> alloc::string::String447 fn name(&self) -> alloc::string::String { 448 todo!() 449 } 450 set_name(&self, _name: alloc::string::String)451 fn set_name(&self, _name: alloc::string::String) { 452 todo!() 453 } 454 set_kset(&self, _kset: Option<Arc<KSet>>)455 fn set_kset(&self, _kset: Option<Arc<KSet>>) { 456 todo!() 457 } 458 set_parent(&self, _parent: Option<Weak<dyn KObject>>)459 fn set_parent(&self, _parent: Option<Weak<dyn KObject>>) { 460 todo!() 461 } 462 set_kobj_type(&self, _ktype: Option<&'static dyn KObjType>)463 fn set_kobj_type(&self, _ktype: Option<&'static dyn KObjType>) { 464 todo!() 465 } 466 } 467 468 impl Device for LockedAhciDisk { dev_type(&self) -> DeviceType469 fn dev_type(&self) -> DeviceType { 470 return DeviceType::Block; 471 } 472 id_table(&self) -> IdTable473 fn id_table(&self) -> IdTable { 474 todo!() 475 } 476 bus(&self) -> Option<Weak<dyn Bus>>477 fn bus(&self) -> Option<Weak<dyn Bus>> { 478 todo!("LockedAhciDisk::bus()") 479 } 480 set_bus(&self, _bus: Option<Weak<dyn Bus>>)481 fn set_bus(&self, _bus: Option<Weak<dyn Bus>>) { 482 todo!("LockedAhciDisk::set_bus()") 483 } 484 driver(&self) -> Option<Arc<dyn Driver>>485 fn driver(&self) -> Option<Arc<dyn Driver>> { 486 todo!("LockedAhciDisk::driver()") 487 } 488 is_dead(&self) -> bool489 fn is_dead(&self) -> bool { 490 false 491 } 492 set_driver(&self, _driver: Option<Weak<dyn Driver>>)493 fn set_driver(&self, _driver: Option<Weak<dyn Driver>>) { 494 todo!("LockedAhciDisk::set_driver()") 495 } 496 can_match(&self) -> bool497 fn can_match(&self) -> bool { 498 todo!() 499 } 500 set_can_match(&self, _can_match: bool)501 fn set_can_match(&self, _can_match: bool) { 502 todo!() 503 } 504 state_synced(&self) -> bool505 fn state_synced(&self) -> bool { 506 todo!() 507 } 508 set_class(&self, _class: Option<Weak<dyn Class>>)509 fn set_class(&self, _class: Option<Weak<dyn Class>>) { 510 todo!() 511 } 512 dev_parent(&self) -> Option<Weak<dyn Device>>513 fn dev_parent(&self) -> Option<Weak<dyn Device>> { 514 None 515 } 516 set_dev_parent(&self, _dev_parent: Option<Weak<dyn Device>>)517 fn set_dev_parent(&self, _dev_parent: Option<Weak<dyn Device>>) { 518 todo!() 519 } 520 } 521 522 impl BlockDevice for LockedAhciDisk { dev_name(&self) -> &BlockDevName523 fn dev_name(&self) -> &BlockDevName { 524 &self.blkdev_meta.devname 525 } 526 blkdev_meta(&self) -> &BlockDevMeta527 fn blkdev_meta(&self) -> &BlockDevMeta { 528 &self.blkdev_meta 529 } 530 disk_range(&self) -> GeneralBlockRange531 fn disk_range(&self) -> GeneralBlockRange { 532 todo!("Get ahci blk disk range") 533 } 534 535 #[inline] as_any_ref(&self) -> &dyn core::any::Any536 fn as_any_ref(&self) -> &dyn core::any::Any { 537 self 538 } 539 540 #[inline] blk_size_log2(&self) -> u8541 fn blk_size_log2(&self) -> u8 { 542 9 543 } 544 sync(&self) -> Result<(), SystemError>545 fn sync(&self) -> Result<(), SystemError> { 546 return self.inner().sync(); 547 } 548 549 #[inline] device(&self) -> Arc<dyn Device>550 fn device(&self) -> Arc<dyn Device> { 551 return self.inner().self_ref.upgrade().unwrap(); 552 } 553 block_size(&self) -> usize554 fn block_size(&self) -> usize { 555 todo!() 556 } 557 partitions(&self) -> Vec<Arc<Partition>>558 fn partitions(&self) -> Vec<Arc<Partition>> { 559 return self.inner().partitions.clone(); 560 } 561 562 #[inline] read_at_sync( &self, lba_id_start: BlockId, count: usize, buf: &mut [u8], ) -> Result<usize, SystemError>563 fn read_at_sync( 564 &self, 565 lba_id_start: BlockId, // 起始lba编号 566 count: usize, // 读取lba的数量 567 buf: &mut [u8], 568 ) -> Result<usize, SystemError> { 569 self.inner().read_at(lba_id_start, count, buf) 570 } 571 572 #[inline] write_at_sync( &self, lba_id_start: BlockId, count: usize, buf: &[u8], ) -> Result<usize, SystemError>573 fn write_at_sync( 574 &self, 575 lba_id_start: BlockId, 576 count: usize, 577 buf: &[u8], 578 ) -> Result<usize, SystemError> { 579 self.inner().write_at(lba_id_start, count, buf) 580 } 581 } 582