1 // Copyright (C) 2019-2021 Alibaba Cloud. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause 3 // 4 // Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 // 6 // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. 7 // Use of this source code is governed by a BSD-style license that can be 8 // found in the LICENSE-BSD-Google file. 9 10 //! Common traits and structs for vhost-user backend drivers. 11 12 use std::cell::RefCell; 13 use std::sync::RwLock; 14 15 use base::Event; 16 use base::RawDescriptor; 17 use base::INVALID_DESCRIPTOR; 18 19 use crate::Result; 20 21 /// Maximum number of memory regions supported. 22 pub const VHOST_MAX_MEMORY_REGIONS: usize = 255; 23 24 /// Vring configuration data. 25 pub struct VringConfigData { 26 /// Maximum queue size supported by the driver. 27 pub queue_max_size: u16, 28 /// Actual queue size negotiated by the driver. 29 pub queue_size: u16, 30 /// Bitmask of vring flags. 31 pub flags: u32, 32 /// Descriptor table address. 33 pub desc_table_addr: u64, 34 /// Used ring buffer address. 35 pub used_ring_addr: u64, 36 /// Available ring buffer address. 37 pub avail_ring_addr: u64, 38 /// Optional address for logging. 39 pub log_addr: Option<u64>, 40 } 41 42 impl VringConfigData { 43 /// Check whether the log (flag, address) pair is valid. is_log_addr_valid(&self) -> bool44 pub fn is_log_addr_valid(&self) -> bool { 45 if self.flags & 0x1 != 0 && self.log_addr.is_none() { 46 return false; 47 } 48 49 true 50 } 51 52 /// Get the log address, default to zero if not available. get_log_addr(&self) -> u6453 pub fn get_log_addr(&self) -> u64 { 54 if self.flags & 0x1 != 0 && self.log_addr.is_some() { 55 self.log_addr.unwrap() 56 } else { 57 0 58 } 59 } 60 } 61 62 /// Memory region configuration data. 63 #[derive(Clone, Copy)] 64 pub struct VhostUserMemoryRegionInfo { 65 /// Guest physical address of the memory region. 66 pub guest_phys_addr: u64, 67 /// Size of the memory region. 68 pub memory_size: u64, 69 /// Virtual address in the current process. 70 pub userspace_addr: u64, 71 /// Optional offset where region starts in the mapped memory. 72 pub mmap_offset: u64, 73 /// Optional file descriptor for mmap. 74 pub mmap_handle: RawDescriptor, 75 } 76 77 // We cannot derive default because windows Handle does not implement a default. 78 impl Default for VhostUserMemoryRegionInfo { default() -> Self79 fn default() -> Self { 80 VhostUserMemoryRegionInfo { 81 guest_phys_addr: u64::default(), 82 memory_size: u64::default(), 83 userspace_addr: u64::default(), 84 mmap_offset: u64::default(), 85 mmap_handle: INVALID_DESCRIPTOR, 86 } 87 } 88 } 89 90 /// An interface for setting up vhost-based backend drivers with interior mutability. 91 /// 92 /// Vhost devices are subset of virtio devices, which improve virtio device's performance by 93 /// delegating data plane operations to dedicated IO service processes. Vhost devices use the 94 /// same virtqueue layout as virtio devices to allow vhost devices to be mapped directly to 95 /// virtio devices. 96 /// 97 /// The purpose of vhost is to implement a subset of a virtio device's functionality outside the 98 /// VMM process. Typically fast paths for IO operations are delegated to the dedicated IO service 99 /// processes, and slow path for device configuration are still handled by the VMM process. It may 100 /// also be used to control access permissions of virtio backend devices. 101 pub trait VhostBackend: std::marker::Sized { 102 /// Get a bitmask of supported virtio/vhost features. get_features(&self) -> Result<u64>103 fn get_features(&self) -> Result<u64>; 104 105 /// Inform the vhost subsystem which features to enable. 106 /// This should be a subset of supported features from get_features(). 107 /// 108 /// # Arguments 109 /// * `features` - Bitmask of features to set. set_features(&self, features: u64) -> Result<()>110 fn set_features(&self, features: u64) -> Result<()>; 111 112 /// Set the current process as the owner of the vhost backend. 113 /// This must be run before any other vhost commands. set_owner(&self) -> Result<()>114 fn set_owner(&self) -> Result<()>; 115 116 /// Used to be sent to request disabling all rings 117 /// This is no longer used. reset_owner(&self) -> Result<()>118 fn reset_owner(&self) -> Result<()>; 119 120 /// Set the guest memory mappings for vhost to use. set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>121 fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>; 122 123 /// Set base address for page modification logging. set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()>124 fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()>; 125 126 /// Specify an event file descriptor to signal on log write. set_log_fd(&self, fd: RawDescriptor) -> Result<()>127 fn set_log_fd(&self, fd: RawDescriptor) -> Result<()>; 128 129 /// Set the number of descriptors in the vring. 130 /// 131 /// # Arguments 132 /// * `queue_index` - Index of the queue to set descriptor count for. 133 /// * `num` - Number of descriptors in the queue. set_vring_num(&self, queue_index: usize, num: u16) -> Result<()>134 fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()>; 135 136 /// Set the addresses for a given vring. 137 /// 138 /// # Arguments 139 /// * `queue_index` - Index of the queue to set addresses for. 140 /// * `config_data` - Configuration data for a vring. set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>141 fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>; 142 143 /// Set the first index to look for available descriptors. 144 /// 145 /// # Arguments 146 /// * `queue_index` - Index of the queue to modify. 147 /// * `num` - Index where available descriptors start. set_vring_base(&self, queue_index: usize, base: u16) -> Result<()>148 fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()>; 149 150 /// Get the available vring base offset. get_vring_base(&self, queue_index: usize) -> Result<u32>151 fn get_vring_base(&self, queue_index: usize) -> Result<u32>; 152 153 /// Set the event to trigger when buffers have been used by the host. 154 /// 155 /// # Arguments 156 /// * `queue_index` - Index of the queue to modify. 157 /// * `event` - Event to trigger. set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()>158 fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()>; 159 160 /// Set the event that will be signaled by the guest when buffers are 161 /// available for the host to process. 162 /// 163 /// # Arguments 164 /// * `queue_index` - Index of the queue to modify. 165 /// * `event` - Event that will be signaled from guest. set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()>166 fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()>; 167 168 /// Set the event that will be signaled by the guest when error happens. 169 /// 170 /// # Arguments 171 /// * `queue_index` - Index of the queue to modify. 172 /// * `event` - Event that will be signaled from guest. set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()>173 fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()>; 174 } 175 176 /// An interface for setting up vhost-based backend drivers. 177 /// 178 /// Vhost devices are subset of virtio devices, which improve virtio device's performance by 179 /// delegating data plane operations to dedicated IO service processes. Vhost devices use the 180 /// same virtqueue layout as virtio devices to allow vhost devices to be mapped directly to 181 /// virtio devices. 182 /// 183 /// The purpose of vhost is to implement a subset of a virtio device's functionality outside the 184 /// VMM process. Typically fast paths for IO operations are delegated to the dedicated IO service 185 /// processes, and slow path for device configuration are still handled by the VMM process. It may 186 /// also be used to control access permissions of virtio backend devices. 187 pub trait VhostBackendMut: std::marker::Sized { 188 /// Get a bitmask of supported virtio/vhost features. get_features(&mut self) -> Result<u64>189 fn get_features(&mut self) -> Result<u64>; 190 191 /// Inform the vhost subsystem which features to enable. 192 /// This should be a subset of supported features from get_features(). 193 /// 194 /// # Arguments 195 /// * `features` - Bitmask of features to set. set_features(&mut self, features: u64) -> Result<()>196 fn set_features(&mut self, features: u64) -> Result<()>; 197 198 /// Set the current process as the owner of the vhost backend. 199 /// This must be run before any other vhost commands. set_owner(&mut self) -> Result<()>200 fn set_owner(&mut self) -> Result<()>; 201 202 /// Used to be sent to request disabling all rings 203 /// This is no longer used. reset_owner(&mut self) -> Result<()>204 fn reset_owner(&mut self) -> Result<()>; 205 206 /// Set the guest memory mappings for vhost to use. set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>207 fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>; 208 209 /// Set base address for page modification logging. set_log_base(&mut self, base: u64, fd: Option<RawDescriptor>) -> Result<()>210 fn set_log_base(&mut self, base: u64, fd: Option<RawDescriptor>) -> Result<()>; 211 212 /// Specify an event file descriptor to signal on log write. set_log_fd(&mut self, fd: RawDescriptor) -> Result<()>213 fn set_log_fd(&mut self, fd: RawDescriptor) -> Result<()>; 214 215 /// Set the number of descriptors in the vring. 216 /// 217 /// # Arguments 218 /// * `queue_index` - Index of the queue to set descriptor count for. 219 /// * `num` - Number of descriptors in the queue. set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>220 fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>; 221 222 /// Set the addresses for a given vring. 223 /// 224 /// # Arguments 225 /// * `queue_index` - Index of the queue to set addresses for. 226 /// * `config_data` - Configuration data for a vring. set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>227 fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>; 228 229 /// Set the first index to look for available descriptors. 230 /// 231 /// # Arguments 232 /// * `queue_index` - Index of the queue to modify. 233 /// * `num` - Index where available descriptors start. set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>234 fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>; 235 236 /// Get the available vring base offset. get_vring_base(&mut self, queue_index: usize) -> Result<u32>237 fn get_vring_base(&mut self, queue_index: usize) -> Result<u32>; 238 239 /// Set the event to trigger when buffers have been used by the host. 240 /// 241 /// # Arguments 242 /// * `queue_index` - Index of the queue to modify. 243 /// * `event` - Event to trigger. set_vring_call(&mut self, queue_index: usize, event: &Event) -> Result<()>244 fn set_vring_call(&mut self, queue_index: usize, event: &Event) -> Result<()>; 245 246 /// Set the event that will be signaled by the guest when buffers are 247 /// available for the host to process. 248 /// 249 /// # Arguments 250 /// * `queue_index` - Index of the queue to modify. 251 /// * `event` - Event that will be signaled from guest. set_vring_kick(&mut self, queue_index: usize, event: &Event) -> Result<()>252 fn set_vring_kick(&mut self, queue_index: usize, event: &Event) -> Result<()>; 253 254 /// Set the event that will be signaled by the guest when error happens. 255 /// 256 /// # Arguments 257 /// * `queue_index` - Index of the queue to modify. 258 /// * `event` - Event that will be signaled from guest. set_vring_err(&mut self, queue_index: usize, event: &Event) -> Result<()>259 fn set_vring_err(&mut self, queue_index: usize, event: &Event) -> Result<()>; 260 } 261 262 impl<T: VhostBackendMut> VhostBackend for RwLock<T> { get_features(&self) -> Result<u64>263 fn get_features(&self) -> Result<u64> { 264 self.write().unwrap().get_features() 265 } 266 set_features(&self, features: u64) -> Result<()>267 fn set_features(&self, features: u64) -> Result<()> { 268 self.write().unwrap().set_features(features) 269 } 270 set_owner(&self) -> Result<()>271 fn set_owner(&self) -> Result<()> { 272 self.write().unwrap().set_owner() 273 } 274 reset_owner(&self) -> Result<()>275 fn reset_owner(&self) -> Result<()> { 276 self.write().unwrap().reset_owner() 277 } 278 set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>279 fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> { 280 self.write().unwrap().set_mem_table(regions) 281 } 282 set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()>283 fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()> { 284 self.write().unwrap().set_log_base(base, fd) 285 } 286 set_log_fd(&self, fd: RawDescriptor) -> Result<()>287 fn set_log_fd(&self, fd: RawDescriptor) -> Result<()> { 288 self.write().unwrap().set_log_fd(fd) 289 } 290 set_vring_num(&self, queue_index: usize, num: u16) -> Result<()>291 fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> { 292 self.write().unwrap().set_vring_num(queue_index, num) 293 } 294 set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>295 fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> { 296 self.write() 297 .unwrap() 298 .set_vring_addr(queue_index, config_data) 299 } 300 set_vring_base(&self, queue_index: usize, base: u16) -> Result<()>301 fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> { 302 self.write().unwrap().set_vring_base(queue_index, base) 303 } 304 get_vring_base(&self, queue_index: usize) -> Result<u32>305 fn get_vring_base(&self, queue_index: usize) -> Result<u32> { 306 self.write().unwrap().get_vring_base(queue_index) 307 } 308 set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()>309 fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> { 310 self.write().unwrap().set_vring_call(queue_index, event) 311 } 312 set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()>313 fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> { 314 self.write().unwrap().set_vring_kick(queue_index, event) 315 } 316 set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()>317 fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> { 318 self.write().unwrap().set_vring_err(queue_index, event) 319 } 320 } 321 322 impl<T: VhostBackendMut> VhostBackend for RefCell<T> { get_features(&self) -> Result<u64>323 fn get_features(&self) -> Result<u64> { 324 self.borrow_mut().get_features() 325 } 326 set_features(&self, features: u64) -> Result<()>327 fn set_features(&self, features: u64) -> Result<()> { 328 self.borrow_mut().set_features(features) 329 } 330 set_owner(&self) -> Result<()>331 fn set_owner(&self) -> Result<()> { 332 self.borrow_mut().set_owner() 333 } 334 reset_owner(&self) -> Result<()>335 fn reset_owner(&self) -> Result<()> { 336 self.borrow_mut().reset_owner() 337 } 338 set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>339 fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> { 340 self.borrow_mut().set_mem_table(regions) 341 } 342 set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()>343 fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()> { 344 self.borrow_mut().set_log_base(base, fd) 345 } 346 set_log_fd(&self, fd: RawDescriptor) -> Result<()>347 fn set_log_fd(&self, fd: RawDescriptor) -> Result<()> { 348 self.borrow_mut().set_log_fd(fd) 349 } 350 set_vring_num(&self, queue_index: usize, num: u16) -> Result<()>351 fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> { 352 self.borrow_mut().set_vring_num(queue_index, num) 353 } 354 set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>355 fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> { 356 self.borrow_mut().set_vring_addr(queue_index, config_data) 357 } 358 set_vring_base(&self, queue_index: usize, base: u16) -> Result<()>359 fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> { 360 self.borrow_mut().set_vring_base(queue_index, base) 361 } 362 get_vring_base(&self, queue_index: usize) -> Result<u32>363 fn get_vring_base(&self, queue_index: usize) -> Result<u32> { 364 self.borrow_mut().get_vring_base(queue_index) 365 } 366 set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()>367 fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> { 368 self.borrow_mut().set_vring_call(queue_index, event) 369 } 370 set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()>371 fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> { 372 self.borrow_mut().set_vring_kick(queue_index, event) 373 } 374 set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()>375 fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> { 376 self.borrow_mut().set_vring_err(queue_index, event) 377 } 378 } 379 #[cfg(test)] 380 mod tests { 381 use super::*; 382 383 struct MockBackend {} 384 385 impl VhostBackendMut for MockBackend { get_features(&mut self) -> Result<u64>386 fn get_features(&mut self) -> Result<u64> { 387 Ok(0x1) 388 } 389 set_features(&mut self, features: u64) -> Result<()>390 fn set_features(&mut self, features: u64) -> Result<()> { 391 assert_eq!(features, 0x1); 392 Ok(()) 393 } 394 set_owner(&mut self) -> Result<()>395 fn set_owner(&mut self) -> Result<()> { 396 Ok(()) 397 } 398 reset_owner(&mut self) -> Result<()>399 fn reset_owner(&mut self) -> Result<()> { 400 Ok(()) 401 } 402 set_mem_table(&mut self, _regions: &[VhostUserMemoryRegionInfo]) -> Result<()>403 fn set_mem_table(&mut self, _regions: &[VhostUserMemoryRegionInfo]) -> Result<()> { 404 Ok(()) 405 } 406 set_log_base(&mut self, base: u64, fd: Option<RawDescriptor>) -> Result<()>407 fn set_log_base(&mut self, base: u64, fd: Option<RawDescriptor>) -> Result<()> { 408 assert_eq!(base, 0x100); 409 #[allow(clippy::unnecessary_cast)] 410 let rd = 100 as RawDescriptor; 411 assert_eq!(fd, Some(rd)); 412 Ok(()) 413 } 414 set_log_fd(&mut self, fd: RawDescriptor) -> Result<()>415 fn set_log_fd(&mut self, fd: RawDescriptor) -> Result<()> { 416 #[allow(clippy::unnecessary_cast)] 417 let rd = 100 as RawDescriptor; 418 assert_eq!(fd, rd); 419 Ok(()) 420 } 421 set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>422 fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> { 423 assert_eq!(queue_index, 1); 424 assert_eq!(num, 256); 425 Ok(()) 426 } 427 set_vring_addr( &mut self, queue_index: usize, _config_data: &VringConfigData, ) -> Result<()>428 fn set_vring_addr( 429 &mut self, 430 queue_index: usize, 431 _config_data: &VringConfigData, 432 ) -> Result<()> { 433 assert_eq!(queue_index, 1); 434 Ok(()) 435 } 436 set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>437 fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> { 438 assert_eq!(queue_index, 1); 439 assert_eq!(base, 2); 440 Ok(()) 441 } 442 get_vring_base(&mut self, queue_index: usize) -> Result<u32>443 fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> { 444 assert_eq!(queue_index, 1); 445 Ok(2) 446 } 447 set_vring_call(&mut self, queue_index: usize, _event: &Event) -> Result<()>448 fn set_vring_call(&mut self, queue_index: usize, _event: &Event) -> Result<()> { 449 assert_eq!(queue_index, 1); 450 Ok(()) 451 } 452 set_vring_kick(&mut self, queue_index: usize, _event: &Event) -> Result<()>453 fn set_vring_kick(&mut self, queue_index: usize, _event: &Event) -> Result<()> { 454 assert_eq!(queue_index, 1); 455 Ok(()) 456 } 457 set_vring_err(&mut self, queue_index: usize, _event: &Event) -> Result<()>458 fn set_vring_err(&mut self, queue_index: usize, _event: &Event) -> Result<()> { 459 assert_eq!(queue_index, 1); 460 Ok(()) 461 } 462 } 463 464 #[test] test_vring_backend_mut()465 fn test_vring_backend_mut() { 466 let b = RwLock::new(MockBackend {}); 467 468 assert_eq!(b.get_features().unwrap(), 0x1); 469 b.set_features(0x1).unwrap(); 470 b.set_owner().unwrap(); 471 b.reset_owner().unwrap(); 472 b.set_mem_table(&[]).unwrap(); 473 474 #[allow(clippy::unnecessary_cast)] 475 let rd = 100 as RawDescriptor; 476 b.set_log_base(0x100, Some(rd)).unwrap(); 477 b.set_log_fd(rd).unwrap(); 478 b.set_vring_num(1, 256).unwrap(); 479 480 let config = VringConfigData { 481 queue_max_size: 0x1000, 482 queue_size: 0x2000, 483 flags: 0x0, 484 desc_table_addr: 0x4000, 485 used_ring_addr: 0x5000, 486 avail_ring_addr: 0x6000, 487 log_addr: None, 488 }; 489 b.set_vring_addr(1, &config).unwrap(); 490 491 b.set_vring_base(1, 2).unwrap(); 492 assert_eq!(b.get_vring_base(1).unwrap(), 2); 493 494 let event = Event::new().unwrap(); 495 b.set_vring_call(1, &event).unwrap(); 496 b.set_vring_kick(1, &event).unwrap(); 497 b.set_vring_err(1, &event).unwrap(); 498 } 499 500 #[test] test_vring_config_data()501 fn test_vring_config_data() { 502 let mut config = VringConfigData { 503 queue_max_size: 0x1000, 504 queue_size: 0x2000, 505 flags: 0x0, 506 desc_table_addr: 0x4000, 507 used_ring_addr: 0x5000, 508 avail_ring_addr: 0x6000, 509 log_addr: None, 510 }; 511 512 assert!(config.is_log_addr_valid()); 513 assert_eq!(config.get_log_addr(), 0); 514 515 config.flags = 0x1; 516 assert!(!config.is_log_addr_valid()); 517 assert_eq!(config.get_log_addr(), 0); 518 519 config.log_addr = Some(0x7000); 520 assert!(config.is_log_addr_valid()); 521 assert_eq!(config.get_log_addr(), 0x7000); 522 523 config.flags = 0x0; 524 assert!(config.is_log_addr_valid()); 525 assert_eq!(config.get_log_addr(), 0); 526 } 527 } 528